From c554cd1844c6ab1e31973eeb13350923f36df1cd Mon Sep 17 00:00:00 2001 From: Six <82069333+sixtysixx@users.noreply.github.com> Date: Thu, 19 Jun 2025 00:43:24 -0600 Subject: [PATCH 1/8] Delete pocketoptionapi directory No longer maintained --- pocketoptionapi/_.py | 0 pocketoptionapi/__init__.py | 0 .../__pycache__/__init__.cpython-310.pyc | Bin 156 -> 0 bytes .../__pycache__/__init__.cpython-311.pyc | Bin 170 -> 0 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 158 -> 0 bytes .../__pycache__/__init__.cpython-37.pyc | Bin 140 -> 0 bytes .../__pycache__/__init__.cpython-38.pyc | Bin 166 -> 0 bytes .../__pycache__/api.cpython-310.pyc | Bin 7247 -> 0 bytes .../__pycache__/api.cpython-311.pyc | Bin 10821 -> 0 bytes .../__pycache__/api.cpython-312.pyc | Bin 10203 -> 0 bytes .../__pycache__/api.cpython-37.pyc | Bin 7179 -> 0 bytes .../__pycache__/api.cpython-38.pyc | Bin 7244 -> 0 bytes .../__pycache__/api.cpython-39.pyc | Bin 6718 -> 0 bytes .../__pycache__/constants.cpython-310.pyc | Bin 4194 -> 0 bytes .../__pycache__/constants.cpython-311.pyc | Bin 2580 -> 0 bytes .../__pycache__/constants.cpython-312.pyc | Bin 5855 -> 0 bytes .../__pycache__/constants.cpython-37.pyc | Bin 4797 -> 0 bytes .../__pycache__/constants.cpython-38.pyc | Bin 3471 -> 0 bytes .../__pycache__/constants.cpython-39.pyc | Bin 3460 -> 0 bytes .../__pycache__/expiration.cpython-310.pyc | Bin 1803 -> 0 bytes .../__pycache__/expiration.cpython-311.pyc | Bin 3605 -> 0 bytes .../__pycache__/expiration.cpython-312.pyc | Bin 3090 -> 0 bytes .../__pycache__/expiration.cpython-37.pyc | Bin 1795 -> 0 bytes .../__pycache__/expiration.cpython-38.pyc | Bin 1810 -> 0 bytes .../__pycache__/expiration.cpython-39.pyc | Bin 1793 -> 0 bytes .../__pycache__/global_value.cpython-310.pyc | Bin 429 -> 0 bytes .../__pycache__/global_value.cpython-311.pyc | Bin 576 -> 0 bytes .../__pycache__/global_value.cpython-312.pyc | Bin 477 -> 0 bytes .../__pycache__/global_value.cpython-37.pyc | Bin 476 -> 0 bytes .../__pycache__/global_value.cpython-38.pyc | Bin 440 -> 0 bytes .../__pycache__/global_value.cpython-39.pyc | Bin 423 -> 0 bytes .../__pycache__/pocket.cpython-310.pyc | Bin 806 -> 0 bytes .../__pycache__/pocket.cpython-311.pyc | Bin 11289 -> 0 bytes .../__pycache__/stable_api.cpython-310.pyc | Bin 10690 -> 0 bytes .../__pycache__/stable_api.cpython-311.pyc | Bin 17273 -> 0 bytes .../__pycache__/stable_api.cpython-312.pyc | Bin 15716 -> 0 bytes .../__pycache__/stable_api.cpython-37.pyc | Bin 12521 -> 0 bytes .../__pycache__/stable_api.cpython-38.pyc | Bin 10727 -> 0 bytes .../__pycache__/stable_api.cpython-39.pyc | Bin 9776 -> 0 bytes pocketoptionapi/api.py | 298 ------------- pocketoptionapi/backend/__init__.py | 0 .../__pycache__/__init__.cpython-310.pyc | Bin 175 -> 0 bytes .../__pycache__/__init__.cpython-311.pyc | Bin 178 -> 0 bytes pocketoptionapi/backend/ws/__init__.py | 0 .../ws/__pycache__/__init__.cpython-310.pyc | Bin 178 -> 0 bytes .../ws/__pycache__/__init__.cpython-311.pyc | Bin 181 -> 0 bytes .../ws/__pycache__/client.cpython-310.pyc | Bin 1366 -> 0 bytes .../ws/__pycache__/client.cpython-311.pyc | Bin 3837 -> 0 bytes pocketoptionapi/backend/ws/chat/__init__.py | 40 -- .../chat/__pycache__/__init__.cpython-311.pyc | Bin 3305 -> 0 bytes pocketoptionapi/backend/ws/client.py | 61 --- pocketoptionapi/candles.json | 17 - pocketoptionapi/constants.py | 177 -------- pocketoptionapi/expiration.py | 80 ---- pocketoptionapi/global_value.py | 23 - pocketoptionapi/indicators.py | 0 pocketoptionapi/pocket.py | 166 ------- pocketoptionapi/prueba_temp.py | 9 - pocketoptionapi/stable_api.py | 408 ------------------ pocketoptionapi/test/__init__.py | 0 pocketoptionapi/test/webdrivertest.py | 15 - .../ws/__pycache__/client.cpython-310.pyc | Bin 7015 -> 0 bytes .../ws/__pycache__/client.cpython-311.pyc | Bin 15103 -> 0 bytes .../ws/__pycache__/client.cpython-312.pyc | Bin 12988 -> 0 bytes .../ws/__pycache__/client.cpython-37.pyc | Bin 7299 -> 0 bytes .../ws/__pycache__/client.cpython-38.pyc | Bin 6834 -> 0 bytes .../ws/__pycache__/client.cpython-39.pyc | Bin 5717 -> 0 bytes .../chanels/__pycache__/base.cpython-39.pyc | Bin 1129 -> 0 bytes .../chanels/__pycache__/buyv3.cpython-39.pyc | Bin 1648 -> 0 bytes .../__pycache__/candles.cpython-39.pyc | Bin 1288 -> 0 bytes .../__pycache__/get_balances.cpython-39.pyc | Bin 750 -> 0 bytes .../chanels/__pycache__/ssid.cpython-39.pyc | Bin 786 -> 0 bytes pocketoptionapi/ws/chanels/base.py | 26 -- pocketoptionapi/ws/chanels/buyv3.py | 61 --- pocketoptionapi/ws/chanels/candles.py | 42 -- pocketoptionapi/ws/chanels/get_balances.py | 18 - pocketoptionapi/ws/chanels/ssid.py | 17 - .../channels/__pycache__/base.cpython-310.pyc | Bin 1146 -> 0 bytes .../channels/__pycache__/base.cpython-311.pyc | Bin 1392 -> 0 bytes .../channels/__pycache__/base.cpython-312.pyc | Bin 1256 -> 0 bytes .../channels/__pycache__/base.cpython-37.pyc | Bin 1117 -> 0 bytes .../channels/__pycache__/base.cpython-38.pyc | Bin 1155 -> 0 bytes .../channels/__pycache__/base.cpython-39.pyc | Bin 1138 -> 0 bytes .../__pycache__/buyv3.cpython-310.pyc | Bin 1650 -> 0 bytes .../__pycache__/buyv3.cpython-311.pyc | Bin 2422 -> 0 bytes .../__pycache__/buyv3.cpython-312.pyc | Bin 2081 -> 0 bytes .../channels/__pycache__/buyv3.cpython-37.pyc | Bin 1661 -> 0 bytes .../channels/__pycache__/buyv3.cpython-38.pyc | Bin 1687 -> 0 bytes .../channels/__pycache__/buyv3.cpython-39.pyc | Bin 1670 -> 0 bytes .../__pycache__/candles.cpython-310.pyc | Bin 1292 -> 0 bytes .../__pycache__/candles.cpython-311.pyc | Bin 1646 -> 0 bytes .../__pycache__/candles.cpython-312.pyc | Bin 1487 -> 0 bytes .../__pycache__/candles.cpython-37.pyc | Bin 1263 -> 0 bytes .../__pycache__/candles.cpython-38.pyc | Bin 1303 -> 0 bytes .../__pycache__/candles.cpython-39.pyc | Bin 1286 -> 0 bytes .../__pycache__/change_symbol.cpython-310.pyc | Bin 1034 -> 0 bytes .../__pycache__/change_symbol.cpython-311.pyc | Bin 1288 -> 0 bytes .../__pycache__/change_symbol.cpython-312.pyc | Bin 1148 -> 0 bytes .../__pycache__/change_symbol.cpython-37.pyc | Bin 1009 -> 0 bytes .../__pycache__/change_symbol.cpython-38.pyc | Bin 1045 -> 0 bytes .../__pycache__/change_symbol.cpython-39.pyc | Bin 1028 -> 0 bytes .../__pycache__/get_balances.cpython-310.pyc | Bin 756 -> 0 bytes .../__pycache__/get_balances.cpython-311.pyc | Bin 1018 -> 0 bytes .../__pycache__/get_balances.cpython-312.pyc | Bin 876 -> 0 bytes .../__pycache__/get_balances.cpython-37.pyc | Bin 729 -> 0 bytes .../__pycache__/get_balances.cpython-38.pyc | Bin 765 -> 0 bytes .../__pycache__/get_balances.cpython-39.pyc | Bin 748 -> 0 bytes .../channels/__pycache__/ssid.cpython-310.pyc | Bin 792 -> 0 bytes .../channels/__pycache__/ssid.cpython-311.pyc | Bin 1008 -> 0 bytes .../channels/__pycache__/ssid.cpython-312.pyc | Bin 885 -> 0 bytes .../channels/__pycache__/ssid.cpython-37.pyc | Bin 767 -> 0 bytes .../channels/__pycache__/ssid.cpython-38.pyc | Bin 801 -> 0 bytes .../channels/__pycache__/ssid.cpython-39.pyc | Bin 784 -> 0 bytes pocketoptionapi/ws/channels/base.py | 26 -- pocketoptionapi/ws/channels/buyv3.py | 61 --- pocketoptionapi/ws/channels/candles.py | 42 -- pocketoptionapi/ws/channels/change_symbol.py | 25 -- pocketoptionapi/ws/channels/get_balances.py | 18 - pocketoptionapi/ws/channels/ssid.py | 17 - pocketoptionapi/ws/client.py | 271 ------------ .../objects/__pycache__/base.cpython-310.pyc | Bin 793 -> 0 bytes .../objects/__pycache__/base.cpython-311.pyc | Bin 984 -> 0 bytes .../objects/__pycache__/base.cpython-312.pyc | Bin 879 -> 0 bytes .../objects/__pycache__/base.cpython-37.pyc | Bin 766 -> 0 bytes .../objects/__pycache__/base.cpython-38.pyc | Bin 802 -> 0 bytes .../objects/__pycache__/base.cpython-39.pyc | Bin 785 -> 0 bytes .../__pycache__/candles.cpython-310.pyc | Bin 3697 -> 0 bytes .../__pycache__/candles.cpython-311.pyc | Bin 4968 -> 0 bytes .../__pycache__/candles.cpython-312.pyc | Bin 4603 -> 0 bytes .../__pycache__/candles.cpython-37.pyc | Bin 3732 -> 0 bytes .../__pycache__/candles.cpython-38.pyc | Bin 3792 -> 0 bytes .../__pycache__/candles.cpython-39.pyc | Bin 3775 -> 0 bytes .../__pycache__/time_sync.cpython-310.pyc | Bin 2696 -> 0 bytes .../__pycache__/time_sync.cpython-311.pyc | Bin 3685 -> 0 bytes .../__pycache__/time_sync.cpython-312.pyc | Bin 3461 -> 0 bytes .../__pycache__/time_sync.cpython-37.pyc | Bin 2651 -> 0 bytes .../__pycache__/time_sync.cpython-38.pyc | Bin 2707 -> 0 bytes .../__pycache__/timesync.cpython-310.pyc | Bin 2471 -> 0 bytes .../__pycache__/timesync.cpython-311.pyc | Bin 3533 -> 0 bytes .../__pycache__/timesync.cpython-312.pyc | Bin 3296 -> 0 bytes .../__pycache__/timesync.cpython-37.pyc | Bin 2438 -> 0 bytes .../__pycache__/timesync.cpython-38.pyc | Bin 2498 -> 0 bytes .../__pycache__/timesync.cpython-39.pyc | Bin 2481 -> 0 bytes pocketoptionapi/ws/objects/base.py | 17 - pocketoptionapi/ws/objects/candles.py | 113 ----- pocketoptionapi/ws/objects/time_sync.py | 70 --- pocketoptionapi/ws/objects/timesync.py | 71 --- 147 files changed, 2189 deletions(-) delete mode 100644 pocketoptionapi/_.py delete mode 100644 pocketoptionapi/__init__.py delete mode 100644 pocketoptionapi/__pycache__/__init__.cpython-310.pyc delete mode 100644 pocketoptionapi/__pycache__/__init__.cpython-311.pyc delete mode 100644 pocketoptionapi/__pycache__/__init__.cpython-312.pyc delete mode 100644 pocketoptionapi/__pycache__/__init__.cpython-37.pyc delete mode 100644 pocketoptionapi/__pycache__/__init__.cpython-38.pyc delete mode 100644 pocketoptionapi/__pycache__/api.cpython-310.pyc delete mode 100644 pocketoptionapi/__pycache__/api.cpython-311.pyc delete mode 100644 pocketoptionapi/__pycache__/api.cpython-312.pyc delete mode 100644 pocketoptionapi/__pycache__/api.cpython-37.pyc delete mode 100644 pocketoptionapi/__pycache__/api.cpython-38.pyc delete mode 100644 pocketoptionapi/__pycache__/api.cpython-39.pyc delete mode 100644 pocketoptionapi/__pycache__/constants.cpython-310.pyc delete mode 100644 pocketoptionapi/__pycache__/constants.cpython-311.pyc delete mode 100644 pocketoptionapi/__pycache__/constants.cpython-312.pyc delete mode 100644 pocketoptionapi/__pycache__/constants.cpython-37.pyc delete mode 100644 pocketoptionapi/__pycache__/constants.cpython-38.pyc delete mode 100644 pocketoptionapi/__pycache__/constants.cpython-39.pyc delete mode 100644 pocketoptionapi/__pycache__/expiration.cpython-310.pyc delete mode 100644 pocketoptionapi/__pycache__/expiration.cpython-311.pyc delete mode 100644 pocketoptionapi/__pycache__/expiration.cpython-312.pyc delete mode 100644 pocketoptionapi/__pycache__/expiration.cpython-37.pyc delete mode 100644 pocketoptionapi/__pycache__/expiration.cpython-38.pyc delete mode 100644 pocketoptionapi/__pycache__/expiration.cpython-39.pyc delete mode 100644 pocketoptionapi/__pycache__/global_value.cpython-310.pyc delete mode 100644 pocketoptionapi/__pycache__/global_value.cpython-311.pyc delete mode 100644 pocketoptionapi/__pycache__/global_value.cpython-312.pyc delete mode 100644 pocketoptionapi/__pycache__/global_value.cpython-37.pyc delete mode 100644 pocketoptionapi/__pycache__/global_value.cpython-38.pyc delete mode 100644 pocketoptionapi/__pycache__/global_value.cpython-39.pyc delete mode 100644 pocketoptionapi/__pycache__/pocket.cpython-310.pyc delete mode 100644 pocketoptionapi/__pycache__/pocket.cpython-311.pyc delete mode 100644 pocketoptionapi/__pycache__/stable_api.cpython-310.pyc delete mode 100644 pocketoptionapi/__pycache__/stable_api.cpython-311.pyc delete mode 100644 pocketoptionapi/__pycache__/stable_api.cpython-312.pyc delete mode 100644 pocketoptionapi/__pycache__/stable_api.cpython-37.pyc delete mode 100644 pocketoptionapi/__pycache__/stable_api.cpython-38.pyc delete mode 100644 pocketoptionapi/__pycache__/stable_api.cpython-39.pyc delete mode 100644 pocketoptionapi/api.py delete mode 100644 pocketoptionapi/backend/__init__.py delete mode 100644 pocketoptionapi/backend/__pycache__/__init__.cpython-310.pyc delete mode 100644 pocketoptionapi/backend/__pycache__/__init__.cpython-311.pyc delete mode 100644 pocketoptionapi/backend/ws/__init__.py delete mode 100644 pocketoptionapi/backend/ws/__pycache__/__init__.cpython-310.pyc delete mode 100644 pocketoptionapi/backend/ws/__pycache__/__init__.cpython-311.pyc delete mode 100644 pocketoptionapi/backend/ws/__pycache__/client.cpython-310.pyc delete mode 100644 pocketoptionapi/backend/ws/__pycache__/client.cpython-311.pyc delete mode 100644 pocketoptionapi/backend/ws/chat/__init__.py delete mode 100644 pocketoptionapi/backend/ws/chat/__pycache__/__init__.cpython-311.pyc delete mode 100644 pocketoptionapi/backend/ws/client.py delete mode 100644 pocketoptionapi/candles.json delete mode 100644 pocketoptionapi/constants.py delete mode 100644 pocketoptionapi/expiration.py delete mode 100644 pocketoptionapi/global_value.py delete mode 100644 pocketoptionapi/indicators.py delete mode 100644 pocketoptionapi/pocket.py delete mode 100644 pocketoptionapi/prueba_temp.py delete mode 100644 pocketoptionapi/stable_api.py delete mode 100644 pocketoptionapi/test/__init__.py delete mode 100644 pocketoptionapi/test/webdrivertest.py delete mode 100644 pocketoptionapi/ws/__pycache__/client.cpython-310.pyc delete mode 100644 pocketoptionapi/ws/__pycache__/client.cpython-311.pyc delete mode 100644 pocketoptionapi/ws/__pycache__/client.cpython-312.pyc delete mode 100644 pocketoptionapi/ws/__pycache__/client.cpython-37.pyc delete mode 100644 pocketoptionapi/ws/__pycache__/client.cpython-38.pyc delete mode 100644 pocketoptionapi/ws/__pycache__/client.cpython-39.pyc delete mode 100644 pocketoptionapi/ws/chanels/__pycache__/base.cpython-39.pyc delete mode 100644 pocketoptionapi/ws/chanels/__pycache__/buyv3.cpython-39.pyc delete mode 100644 pocketoptionapi/ws/chanels/__pycache__/candles.cpython-39.pyc delete mode 100644 pocketoptionapi/ws/chanels/__pycache__/get_balances.cpython-39.pyc delete mode 100644 pocketoptionapi/ws/chanels/__pycache__/ssid.cpython-39.pyc delete mode 100644 pocketoptionapi/ws/chanels/base.py delete mode 100644 pocketoptionapi/ws/chanels/buyv3.py delete mode 100644 pocketoptionapi/ws/chanels/candles.py delete mode 100644 pocketoptionapi/ws/chanels/get_balances.py delete mode 100644 pocketoptionapi/ws/chanels/ssid.py delete mode 100644 pocketoptionapi/ws/channels/__pycache__/base.cpython-310.pyc delete mode 100644 pocketoptionapi/ws/channels/__pycache__/base.cpython-311.pyc delete mode 100644 pocketoptionapi/ws/channels/__pycache__/base.cpython-312.pyc delete mode 100644 pocketoptionapi/ws/channels/__pycache__/base.cpython-37.pyc delete mode 100644 pocketoptionapi/ws/channels/__pycache__/base.cpython-38.pyc delete mode 100644 pocketoptionapi/ws/channels/__pycache__/base.cpython-39.pyc delete mode 100644 pocketoptionapi/ws/channels/__pycache__/buyv3.cpython-310.pyc delete mode 100644 pocketoptionapi/ws/channels/__pycache__/buyv3.cpython-311.pyc delete mode 100644 pocketoptionapi/ws/channels/__pycache__/buyv3.cpython-312.pyc delete mode 100644 pocketoptionapi/ws/channels/__pycache__/buyv3.cpython-37.pyc delete mode 100644 pocketoptionapi/ws/channels/__pycache__/buyv3.cpython-38.pyc delete mode 100644 pocketoptionapi/ws/channels/__pycache__/buyv3.cpython-39.pyc delete mode 100644 pocketoptionapi/ws/channels/__pycache__/candles.cpython-310.pyc delete mode 100644 pocketoptionapi/ws/channels/__pycache__/candles.cpython-311.pyc delete mode 100644 pocketoptionapi/ws/channels/__pycache__/candles.cpython-312.pyc delete mode 100644 pocketoptionapi/ws/channels/__pycache__/candles.cpython-37.pyc delete mode 100644 pocketoptionapi/ws/channels/__pycache__/candles.cpython-38.pyc delete mode 100644 pocketoptionapi/ws/channels/__pycache__/candles.cpython-39.pyc delete mode 100644 pocketoptionapi/ws/channels/__pycache__/change_symbol.cpython-310.pyc delete mode 100644 pocketoptionapi/ws/channels/__pycache__/change_symbol.cpython-311.pyc delete mode 100644 pocketoptionapi/ws/channels/__pycache__/change_symbol.cpython-312.pyc delete mode 100644 pocketoptionapi/ws/channels/__pycache__/change_symbol.cpython-37.pyc delete mode 100644 pocketoptionapi/ws/channels/__pycache__/change_symbol.cpython-38.pyc delete mode 100644 pocketoptionapi/ws/channels/__pycache__/change_symbol.cpython-39.pyc delete mode 100644 pocketoptionapi/ws/channels/__pycache__/get_balances.cpython-310.pyc delete mode 100644 pocketoptionapi/ws/channels/__pycache__/get_balances.cpython-311.pyc delete mode 100644 pocketoptionapi/ws/channels/__pycache__/get_balances.cpython-312.pyc delete mode 100644 pocketoptionapi/ws/channels/__pycache__/get_balances.cpython-37.pyc delete mode 100644 pocketoptionapi/ws/channels/__pycache__/get_balances.cpython-38.pyc delete mode 100644 pocketoptionapi/ws/channels/__pycache__/get_balances.cpython-39.pyc delete mode 100644 pocketoptionapi/ws/channels/__pycache__/ssid.cpython-310.pyc delete mode 100644 pocketoptionapi/ws/channels/__pycache__/ssid.cpython-311.pyc delete mode 100644 pocketoptionapi/ws/channels/__pycache__/ssid.cpython-312.pyc delete mode 100644 pocketoptionapi/ws/channels/__pycache__/ssid.cpython-37.pyc delete mode 100644 pocketoptionapi/ws/channels/__pycache__/ssid.cpython-38.pyc delete mode 100644 pocketoptionapi/ws/channels/__pycache__/ssid.cpython-39.pyc delete mode 100644 pocketoptionapi/ws/channels/base.py delete mode 100644 pocketoptionapi/ws/channels/buyv3.py delete mode 100644 pocketoptionapi/ws/channels/candles.py delete mode 100644 pocketoptionapi/ws/channels/change_symbol.py delete mode 100644 pocketoptionapi/ws/channels/get_balances.py delete mode 100644 pocketoptionapi/ws/channels/ssid.py delete mode 100644 pocketoptionapi/ws/client.py delete mode 100644 pocketoptionapi/ws/objects/__pycache__/base.cpython-310.pyc delete mode 100644 pocketoptionapi/ws/objects/__pycache__/base.cpython-311.pyc delete mode 100644 pocketoptionapi/ws/objects/__pycache__/base.cpython-312.pyc delete mode 100644 pocketoptionapi/ws/objects/__pycache__/base.cpython-37.pyc delete mode 100644 pocketoptionapi/ws/objects/__pycache__/base.cpython-38.pyc delete mode 100644 pocketoptionapi/ws/objects/__pycache__/base.cpython-39.pyc delete mode 100644 pocketoptionapi/ws/objects/__pycache__/candles.cpython-310.pyc delete mode 100644 pocketoptionapi/ws/objects/__pycache__/candles.cpython-311.pyc delete mode 100644 pocketoptionapi/ws/objects/__pycache__/candles.cpython-312.pyc delete mode 100644 pocketoptionapi/ws/objects/__pycache__/candles.cpython-37.pyc delete mode 100644 pocketoptionapi/ws/objects/__pycache__/candles.cpython-38.pyc delete mode 100644 pocketoptionapi/ws/objects/__pycache__/candles.cpython-39.pyc delete mode 100644 pocketoptionapi/ws/objects/__pycache__/time_sync.cpython-310.pyc delete mode 100644 pocketoptionapi/ws/objects/__pycache__/time_sync.cpython-311.pyc delete mode 100644 pocketoptionapi/ws/objects/__pycache__/time_sync.cpython-312.pyc delete mode 100644 pocketoptionapi/ws/objects/__pycache__/time_sync.cpython-37.pyc delete mode 100644 pocketoptionapi/ws/objects/__pycache__/time_sync.cpython-38.pyc delete mode 100644 pocketoptionapi/ws/objects/__pycache__/timesync.cpython-310.pyc delete mode 100644 pocketoptionapi/ws/objects/__pycache__/timesync.cpython-311.pyc delete mode 100644 pocketoptionapi/ws/objects/__pycache__/timesync.cpython-312.pyc delete mode 100644 pocketoptionapi/ws/objects/__pycache__/timesync.cpython-37.pyc delete mode 100644 pocketoptionapi/ws/objects/__pycache__/timesync.cpython-38.pyc delete mode 100644 pocketoptionapi/ws/objects/__pycache__/timesync.cpython-39.pyc delete mode 100644 pocketoptionapi/ws/objects/base.py delete mode 100644 pocketoptionapi/ws/objects/candles.py delete mode 100644 pocketoptionapi/ws/objects/time_sync.py delete mode 100644 pocketoptionapi/ws/objects/timesync.py 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 43270737fcb350d38ec8b9503302a86eec67104a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 156 zcmd1j<>g`kf-RcjX(0MBh(HF6K#l_t7qb9~6oz01O-8?!3`HPe1o6vWKeRZts93)& zGd;gNF(*5>P{wCAAY(d13PUi1CZpdc_`t=4F<|$LkeT{^GF7 h%}*)KNwq6t1)2u3ub3Z5d|+l|Wcg`kf>K!yVl7qb9~6oz01O-8?!3`HPe1o6u>*(xTqIJKxa zCM+{OKgK1sIJ+djASNllB&HxgIXe}|Dap*wODxEYiI30B%PfhH*DI*J#bJ}1pHiBW MY6mjzGY~TX0I2IC%m4rY diff --git a/pocketoptionapi/__pycache__/__init__.cpython-38.pyc b/pocketoptionapi/__pycache__/__init__.cpython-38.pyc deleted file mode 100644 index 67ab17c1d56596fd4881563cf6952528e03eec69..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 166 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 diff --git a/pocketoptionapi/__pycache__/api.cpython-311.pyc b/pocketoptionapi/__pycache__/api.cpython-311.pyc deleted file mode 100644 index 61e04fd5772473d08ec8641f11786d9d9ac39c62..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10821 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 ZAXDFXS?1}7?FlzC@b>Jt1XDVL{{uF%HMIZ$ diff --git a/pocketoptionapi/__pycache__/api.cpython-37.pyc b/pocketoptionapi/__pycache__/api.cpython-37.pyc deleted file mode 100644 index 816eeb4c199d4172c4f36d7401f9f56a20c0e50a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7179 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 K(KG&XR^>lS(cxVH diff --git a/pocketoptionapi/__pycache__/api.cpython-39.pyc b/pocketoptionapi/__pycache__/api.cpython-39.pyc deleted file mode 100644 index 68af619f2ce05d0c32631f044df1b7a7d976a54a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6718 zcmbVQ&2JmW72jPh$rVL?Sdwj7wqiOTj+r=&V<%~=xUN4EIZk8MtpvCo7}%^hBWdO3 zE)v6G?#I{|`a)1?jk9r+c-ld=HNhq{UJNGJXsi@WwWis$9xDe&S~F}W zdq1n+JHcG*C_Adh#)4z5WwWg z+{BC4dKBP!oQn;w6U5wa#!ZXX%zlKERtf-h7R@)1)qP7&$Hq726LZywjjxTb%?*oN zyztba5pLkS?3EWLsGER+AMu$5s7W;70dDieiDY4|NE*dX$3vlF!Cl(UbK|*{m|BWw!I53NDBu|2IA9)}PoQ4_oCKT#ECOBwz~)#Da0c)?;0?f=;A)Gvu+rI_|J!Mxe2yPQ|0!l2nA1z6Mw8zJDX|l+u3Kj9RTu=r=v>hY$UDYlLgA zyddXzK^Cp;_K@Gu>l>u|FVJvv&v4V$jII5`GXu{yxAu%}+bw2&X^*H7z0f-G*nUM* zjmOnJ$RwQ)KPhy7SgB>drM4%%R*f8`)|SyDU#O+pxy)VAWdU6T|(R8G{$ zIjk(#Cz9!hy`bVYbyVpeiMOQic;aL%R8n3QN+CieWtAbN9U=XV-DE759TmGG+)2vW zc9O~U&aSIE%_e9!IzdwQ6ylK|C6k*1q&tXVZV*N7#HLn~JnL3sD-mqSGk8dSPdgy_ zUgEf}ANsND{t{1W2905t%#u|hH#AGdl36jk^F!Kg^d>baEcJeb6KE}wD?=L;z07VK zfd%0v-mS$^4g0L+0%}xp(Je_4cVq~OlbHQb#a`GHwP>TZ)FfM4`UL*0}M*uZmBQj{J3=COSCZ}3*>2wm!)82-qdDlyiYMNsyB4iI|QAG!@BHuhnL z&&XQe50Fsk3Oy^8B7H&p$vQiHmMIcJ{}f8IP&2?3JIHrmX&Y6+F|4( zH8zE-z+~w__UYTqCSCvVD;3LH=(R+uom@2e>h$hF{{4>BQkyTEgjWZ1nDybswdIxi zR8kJ2%}qbtOvYd1T=7MKnj>oL7XE^s&F^*fL% z@?c}EQkb@;Ez2ww9J5lGf$Mh94%_l*CIjwEZa`kEt@`IcV9SP!ViB#yVdHKgw)cy$ zS`$5jtF}>dzYMF@)|@>f%$i2`MyzlnC-Ef7Jkz{={^ZBQqZgv6AYWdQm$AosS!+&y z09;-~lN7aLJREcUkeB15I@Y59Tj_*{RjDpra@~o4%TSg%$FOiDOeS6-0NOIg^3y| zuPWHMNJyIE-?^~L-BZIF>nHqnl1kNPvf}m5?#`u=%NJ1{?pzv!A*M>Q4qJ+*_;w3I zHNSs_YI1egn&}C-hC^g1g8xZ1kZ5+09@I~J@YLCOs2ao_?17}qk)E=-{d$rV{zgit z0-0xXaa)mzsN7u`p_+{BTg&MWhvbwp?FG9&S?Rk1SLw@w|3_l|mkon5>$m9vdG#;Z z=$}6zHwqFXJ0 zm0DKTm0g!dP1j{}>3xiw`X@RO3N9&R$m`UM5ub9VAhv+(h#sjv$aq13^sR9H!WLit z&fU4l<~ku4GT23>=>|RyT2zCv>2{=isx92~Te!Z8K!JusA=Aea*=f-!>p=$vq;fa0 zJ?ZKD6`KAg9b9sXXplmB57R~78lW2DlV>+J4p#nsp?AmT1DqR$T#r17yBvYLmEP%c zJd$|f>XRTFKVYrc(G5RDDDqJc`yqmBOP|46i4KuIPpZ_XlYP#rv7`kQG!*fql8%#1 zlChplRO0kdHicU*R7<#6(v+%Ca$AsQBzM_tkJ!ydNbIxTj^_v7dVueuvI(WLVw34= z&}vfzsMTh5{&(~FS~U|9$U}c~D|R=8VDsGH^kaNlW-0B!YhpJ(mz0FrBqcR1s8r@> zRA*f^rN1tyif{S)(A`sVN^%6HLZGV)w`X6c4-L63?-3b;8`C$iV<}~y9q3Yoojx>9 zmZ|(MmZv&2wcJfQ(;zIRpP+h{3m*s|SAFNE2isA|*<9Sl`6e?LK^+)f*!q{~1u)>*8aud;J79h@ekXu*H{Y&S{YNV=t@lzG@|Fd29q z94O0qGzC1fLkg?*QlSgeXR{sDZ*G>!Gvj4v`pudM$WRCDo6XQLs+81j{J@WQlWNbR zw1HN#rxo!qnI0|@<=Yf79IUJFAkw<)6AC3)si8tJEyI=xyGhOG)ZC)x3u+EZ$AkLu zI9|{M^$RqPX%}o%jwMu%HUU-d_ya4X&onmj59$hzY}sGW&FFGo1M}?4tYucpl|^U4 zIpNrJVyILA)H{|bXvY|%8am?QfT6Ptdwm469%XM;orF#e6ZT(5=o1aA50kt!FUxU!h@7B? x?!%K(x}?_UCPYuu3FB?s#Pt}bG6`9{{yn-Q!D@g diff --git a/pocketoptionapi/__pycache__/constants.cpython-310.pyc b/pocketoptionapi/__pycache__/constants.cpython-310.pyc deleted file mode 100644 index 8f087724250e5d9d47599d52b0c0028ac8e1ad91..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4194 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 VzXpF5gVBG2KMmer|1x1(_#1Pj$Rq#& diff --git a/pocketoptionapi/__pycache__/constants.cpython-312.pyc b/pocketoptionapi/__pycache__/constants.cpython-312.pyc deleted file mode 100644 index d0ac14781e260c587b6da4edda58758023c55e44..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5855 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 M%%4zD_%Gyt0T=yp*#H0l diff --git a/pocketoptionapi/__pycache__/constants.cpython-37.pyc b/pocketoptionapi/__pycache__/constants.cpython-37.pyc deleted file mode 100644 index d1c778572247b69182ee6d73819952fa0b88c5ac..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4797 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 LDF4!)RwDlfT9a5s diff --git a/pocketoptionapi/__pycache__/constants.cpython-39.pyc b/pocketoptionapi/__pycache__/constants.cpython-39.pyc deleted file mode 100644 index 619aef0e3a87a94292711e78579912df0d66bdb1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3460 zcmd^>`)?dq6~|}x<@GvAO9=D@#l>loZ5w;zN17&flG&Gc{TOdOv&m-avaI(^l1bJ( z?##Hsi=m}9v_c>X<<*kXR05$E5FjCtKtcjq3J8hEkNYb>?a%yxR0)amy?54ef&YM6 z&F4Jtz2}}8dv0U@{*=Pk1Dz9>or8+ zG)O5LqJ6ZV4$vKRC%umj();NUeSkhlchTK+58X@m(TC{6bU%HBhUo$ND2>ozdXSFL zQ94FxdWasTN9Z_>(immv1dY=KP0|#dq(|u#Jw~VL3{BGvouzYho;1o*j`CEXBF$2X z$}~swv_Olb(-KwaaatyWOsdiax=1y;L>4_kEA%9NjOz4pYS1b*iO8nQbcL>xL)Yj! zwP=mni4+44M2v7iuz%gK!;hey* zaSZ!8%pbt~4e(pwE#Ph7cZ}ZKNO=cZ8WWU&hk!EhFfhk({(uoh;8ox(@O4J-uZU)0 zz6|^kI1PLSI0t+KxRcR)1oIsSMuA0O4A2?QpOBJ)xdc>z-vjJddRc}ujj6s0d>hFV z2s{o<0#m?AzywY)oIfLZ2KXBA7a#|`!sz`BDL;gE2_r0+e?wFQz6s0$uQ7V#Ncj$M z9{4UW13Uqo1y+D_z>~l`4CfZcUV*6r-vcfK5#SAm^C;H%67U%CW#AO>A|t)siH~Sn zWuY@DMBNRVo!E$0t8|i}u%jbctuw$7R(vF9)V(=p3`!Z87sjx=TM@Q zaw^tEiBg8XQc?PZ6uuM>)R*Rk9?O-@vXum7etsf@a#B(XUox*5rMg%=KUbc}Sc5`@ z`Aa4uiNeH`7$>ES6{h4|QieXWB=f7w+%XkwlAf~L%iSfkzT`D+mK$#+SaO1ul2XoG)2d;t8pa^Qm>%W}8Tu;n53MF- z-zVO}RILIDUz7jaz2}Otb)Ih@p9Wj8R8sIUrii0jQ5;rw6e~t4n~y9vRUBG&;#PtN z`C~oQO)M8?%S*bozp9tbLcVU8nprS9+6~`7JvP=@caCrP$J?$KTpe9^N7owObvqdI z-6o4s#~nLYC@;-S9tpfg+h6CbnH%=1j5hI~xqm?)!I#my~uS!-p`gI;y{`9_`$Bbl6?JW;cW3s||m+ z?G87Bz;jkN0({3Fn0{r^CA(E$DC-4nd1YN}-CYkHx6R#IX}WDcXtV==bbYfsRIgvL z1JqHkzlN>iz@w-Ubszsa2W3U0dy>qw+8yNHk-O$}?Eh*R9;|2?9_*It-5+qHxH*wq z(btu26_1$DUePZ`jQ9AtMKQg}U%lN6I-Y^Swy}stsMhp7Bdi|-5MoUIS1zwFO>&8}Hx8*fh-LpDg0}osGaDw_g-=bs5RP3Lz yL&=2jrlTH?Io$y*XO=G%4B`A=b8!1)6*%lE{sCq(8sRtM|Ni%^@~^}*O7h>S2vjBj diff --git a/pocketoptionapi/__pycache__/expiration.cpython-310.pyc b/pocketoptionapi/__pycache__/expiration.cpython-310.pyc deleted file mode 100644 index 70f0d08699710dadf2c8ce899e58f364527226ee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1803 zcmZ`(OK;pZ5GJYT%4>J+7_oav1s4IjXkjF1fdWQRq;PUeg8)S@i!Bz6NN;3IltOXq z2c%24hhB2-)qBjjKf(V(*Piw-6g_l?@*52W4Tl`gli#pdU5yBgowKJ0;}t^w#>MTy zVDSw8>Ln0C1kK0@XO_{Nj%dzCjQ-!cJLd@ZcQSH>Cw!P)5r`0GPtc#pu(t$@EMA$m64FVwH8)JDcL6&G&~r77Yo zqcM-sYGrs{?t(WbRi<-fw}!NJ@#qJl1F-zhv*&x`*Qd!_uJhNrIFyMQzc>NaOiq57 znPQq1YFrk{5x6K#S}0zoV|h}hn&WD>JZ;y}+{6@*fjwR>rHHIc?n9g#w1N$57!YhOy7HWR~$*W>7SlmrzC-cj+bR#a}AUFBVmt zNS$z1#BX@TMG;GdWxviADi52du*YLD)7&cVTJtYHX0}3STE<4|JXKs@EYK=MQOW5{ ziDK{TClE;%gAguZH?r3DO0Ic+<9IK&_Al=`PF0$uJd5!Zno2Gfm>g(hrkBbXAYR;F z{PI?`;H#2cfEr|MT~?je@)2B+Mm&CCtzXHcP@-MQ(?l2DVrgZx4Xm21mUz{GG4E|L z)78+|xNcVubdg^dOe3V*K+7`YP+CN~g~`y<2oC6v%OKlO6~_~VR~y2mblm0*`fd$Y~N`?d?nD5Md=8VyhbUlDWVk7 z3D{%?k;jZDp!X4-b2!1S<_Al{uZYrH0`JrNvv`cDx7*WV_ar0FRac(l{2gY%&7fyWU2 zn&>>jM{JuMFwF4XA9VozxcbT>Ov8Ij^cs>+kl+>Qbs$Sb#S*1w?D3CF6sBLnl`QcY z8Ln7}=?B2;O(eK$@nn(JdYY@LY<*sq62Kc)Mjs#)+n!XjTtYj@*2RrsWZ|bDqW%_; z)&T^ybOKMPIBC6933)OKwamFn)wHE%#Du<&ymdt0)fo%Wr(ml321rEL=p#CyYmQ6% zzz?VkAMS>9*HYmAb^QSgam8W^$ov8np^bK6>Jq(f7YkY|zZC<%>DsnH_+4IzSth^7 U4y~}PAsNs?5Yo^Iz5Zwa0r9xLy8r+H diff --git a/pocketoptionapi/__pycache__/expiration.cpython-311.pyc b/pocketoptionapi/__pycache__/expiration.cpython-311.pyc deleted file mode 100644 index 5204f4318f454d84368f600283a729e4eb943c09..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3605 zcmb_eUuYXg8lPQhrIl9BT3)qwYq#F~bxc#oj&pXfopKi^H0d?{LoSZVsWn^e+F4s# z>1Nm0#7I4H^g%U&*w9i3E}%R#=MWzlN+B-IToi z@c7_)b{5Fj$ZDF)qGD4IrmO89ydTs$=pOJqsPn<*%u@ek+W4!kVM~)24LSYEyk@24 znj*3y6HQD@No58=L!WL~73JtqKjZlct4T!4KVO_2i(fW1Y{vg+=yTVs_*urzx^fk;BQE6Wy%tmm4_sO*OC9;|p!UzPJ2N3*OIYA~z*4X!l~OECFcBxV`a zzd&*U;g_?%X4zzB37quY4&bz$ua#E~Yy@}dLAfkQ`&!HTS~+t5tRJ$5Upx#>3(L?Z zm1JTusmNLqGDS;eAa^vm632x8=vzZhJ-Tbk2@NL{BQ1ZTn2MT~HG||@J*<#!Fn9yT z6j@!w3SBrr#h?2nu_oCJYqF)`lx`^a(On`Hv@o@~MMF)G4G)4QNf{;=t@J>cN*l{4 zSV=WD9+Rp3YJ$tUp(k`DDHARtD)HzpF%DJ}ORI~Cf+R!7nC)UXU)+%pJlD2aCIwUc_d90FzYn+GcS& zRd*N`$yg9;nWO@)C8G4MT(;0G(JRX8l`HD4;S>tG#3z!Xd7Z$J< z2~V}PoLjc})TzD=f9ts;2+=yy+RGwx>Q%0a3Nb*Orc=DPyc}ytyZ8co?VDrg06Q!K zyVa)d!i}@&qftNxU$M!$8ZSbvcDCx>+GEfzefIQ2j=gzG<#SLxbAjdx_$xU68>5sB zWVtHV-)@c=;6C55tMR3VTJ317ts;jzuzkS#$=YItXN4S{U=I1awZaXCKAslf$;JP3!>F!UnCmL(X;g$xe* z6)i&9;NH2F4Yl}%Z}oihob^&eqj_?TpzYUgM?{lZwpAj7Zq8(bPyyTCqnM8OffyVm zgy143hXluf>=2`N$QfK>4`19Ncq~9S}-7ip3$1lS-?j!^fq=MiS%RLS&#+>E_ zluSm0a>1Jxo+aS#g*0O^r9lDGJUf`{Sr^P;;H=kC%mv7d2Q|5H#Z(e?Y?oRZ`=j?`nVIBa3d2Af7<0wKhz!m?H^BU z1uvG7pBsWIap%IiV)qO>J%cV#X^@l%zinQg=y{LJmu()z?`3G)6V#mJ3i%%UU5gSY)}8$j%&{J ztUEpHjLzEg#@2{InM)Dz);r7ZYPc^GUB*VO1=mbe1#87!oHRL%cr2IhB`}8Y30&dboKJ; ztA#U#Gf$(jl@C{6Dx4|t?YG2bajoOdrDF8<)IX-anf#Z3BXnQb5S&9N-9smx*G}49 zzjOEzmmjg||EVD5->2mj?k-0ldn&n3@}AyN=p}-t)_p|TX!`xkPo&c7Vp4kx4+1B- z6PbA+Wgo*ZB_!nOuY``;d;QD%5YxZsOnqrNz%u*6y)_cbNBF diff --git a/pocketoptionapi/__pycache__/expiration.cpython-312.pyc b/pocketoptionapi/__pycache__/expiration.cpython-312.pyc deleted file mode 100644 index eb86a1ec0f7ffcc94ade2117cc566b899476a0ae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3090 zcmbVOTWr%-7(U0b6DLlZrpZRPvenRWiD11@1`~9fMqyk8yKz~?m?|%Ji`&G`j-5d( zXUnEZYXOO(AyH{O6p4qTR$8U)fyYg}OxjCAG7))5LmDrAt6bQZjRXtJ?D$^L*k*a^lIZRvIHN%xxFzdpq>;UgzM3}Y)P#hsG|S_jWLb-a z$n%*E;GTd8ZBR-kGD%5Pk`Qerm4^5$VkI&O_15>An7Vb%5EBYXNSZEwEE$rli;70` zr4}ei8Q7eFJxP=^L~9k`&=E zW9r=jN7}BmM5I(*@h;JkUrka`)zpM4B}Ga_MI~-sqn872debU1HIU}qF7a@KwV=yT zBpKM?#*Rv(!nOP&_>j;L-(YO7p(J!owuO|MAbMr7O!NN+wtWtpVk}cyC=Twlo*_ou zOK3XUp%J2|Y7Qe|y5}QGIw^r`Y2rD_9_I*6GbsM5kv6aCXNGBI*gOQKGKg+GX`tL*s3ekZd_vDWq&qoJ} z(Gyd=BVuOVmljO?k;@0?PA^5SB}=jX0^jePa-qxqIb3R8Utrhg4&3k9IzLqG*pX-3 z?+KlGwv*7bU4?Y?K2A+1Vf+s@jV(Mzz0%a6YVjgdt6IjaRdz$gG4<+^J&WuD4Kg%Z z$L`$yT8P%B%sF-23J6fEyJpTTqlH(Udsj=&GNVw{CWSnw$+e2!i$2-{70+6pF`7C} ztfY^*7{S(DErgYk4is!Ot^w9|Mv`n#_l#?04;T zzj9#G3;TF{Gt|bOI{%aITBoWbyAvD1iB0GK@}5`r(5Ppxx#t<$GMjEq}vZdDmz4J@Xk? z`xt#Yo7zHk?ebC0BaFMk=)y4;Sw46tLT@|BM_Pb^(6AAT9)u<+p>mgLc`eg?pJX;1 zS*EF}ZiP}=rid079Oxs@!knz30|c~!a}MI!9Feq9#r8_+v;v^Y8zvc{5NZ1rEt66J zrWKo|8}Wbxi{v@V|2zz~2awIi!|+ccpS3wvla;eJPn49TsoJQGO`DO40k^6kVgoEj zC@i{!4tkPqAnSu}EP>@YbT_VOb1HQm*$3w(e4u8(=3DoI*=rUa)aeyD7WvaY%K@EGhOGl+=aiV zvv%+#JT-+D1T9iDBw zdUP)G?TPDyi|lv)8~z2M_|o>m3)}PUul&mISP?+jN(&0ca_=~=1w67FA%K;1t39U9 zYv)Aw26D)phw%|VkkaK$QhA;90uRM?V?7Kj9*psCNcbIX{;LJyI7}aS8NBX6U@aCO LzTSyrj*PznoXfO& diff --git a/pocketoptionapi/__pycache__/expiration.cpython-37.pyc b/pocketoptionapi/__pycache__/expiration.cpython-37.pyc deleted file mode 100644 index 384058d51799ec5815d4ea1ff58b0bd750a3ee5e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1795 zcmZ{lPj4JG6u@oI-<>4eWC=}9#fKJYr7EIDqEdtqA_Z|My#S50S`?hIvrKpAPd#4P zRx_vNTB#pl_Xgs~N65FBE2sSeJs=Le=Sempq9fUUezxCxwtvs_#nx6t;QQx~;n{G5 zkiW2V`7sbahf+R)N)kyYWPmN3(3B2n$_9-7U%#E_NcUGVaHJ=FXk8h|5L!>tZ%Mzi z0*mBnpEaQrMwuj4O$TeKCPu^*N8!WPfpP?;+=R-IiY(}o%+Uya)j!I!pA)5x;?s=h zr_Yti`K3WFI9e=?NQ(pTUJW<{a}_6~}d%??^QG5N{b+ZKk4hew$1Kpk5d1P_V}#qOs7 z!N6`RvZOVu*mEZ7nCQD8Ig&~yo!6{(vi*t~SGv-BP4~$4k%~%p?CNKgV_cN?3C?*$ zY6msamqFVzUggYb?N%IIMP6SsII46}BaB_;bTZ@zPKQo@5FH0UP z9g8gIr=k=x=PJXKUhfnR8Cda;zsE-$%H;%qE(2ZRHJDo z^TUrnf=DJD2B#)Jh^)24LTHg*IX>jp{>An3BunB%OgPR$Q}Ou{HwUY6rx%McK)kd= z{^VM;;Hy+Z!ZO&gby>EnR(IfnG$IJ__@#>TOg0;766?I}tn3(V0=rC=E4*gF%KIsw z>ay=^>^ECyI#1UJrXj~j6KGXT1RO2GZ4v2v8sUI+xCpWdv;3D6M5_tmQd(*Eio7e@ zEWT|OqKWrL%ADMo*n@!*E7FOah;FleCvaig3<8s1T_34g*0p`VomsOHFgm+$fY3R8On%_Nrn3mp-kdHe zCI4Oh;`Ervu!e`D_TLrF?8nNlyfJx0wQrn?EkfB@LPRpU4bRq?>9=jX7$uBDv=HeA zMEdrIXJ0ydr1oz_@Y^wgn1k7Orh~ZzF@HT|wO5CgCxfvEUO$l$$ea6Qm(-mM^=PM4 z1&g5SfX5L1nrIQ>+qF;581C~8eR2DkQ~~7W*3%2pEFc=+I*rdrpXx5kcrW@ER4c?} zg%rJF_r6)7D!C8$w8D3#zhRlvJ0NSkZjD+Cm_@Dew8%!P@kLQ6pgAm!K0`K}j>@K~ zg8Wd8i;8|^dDOeO{(Yz#2asy$2|S~6)_6%K)oc)Im5MCMMh!IsCiP7ecM!$PIhNQ5 zV5)ow6`>LB(mQn9=~5S@F6}|C=!9PQ!N2ZizghqQ diff --git a/pocketoptionapi/__pycache__/expiration.cpython-38.pyc b/pocketoptionapi/__pycache__/expiration.cpython-38.pyc deleted file mode 100644 index 3e45a9b8dc98024c36424ec0165bfcc0b43994de..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1810 zcmZ{l&2HQ_5P(TaT5;u{+KCanmsAKEpxYm#KZgQ~qG;i?Xwf7MP^1VJTWlGT-YC1G z6pGt8fppql1LP5Qd&{|x;CJXj*Piy02k4>b4CUC4(Fov>oFRuZKOWW_$L=B3)U3prJfHa-r0K}-mq|G9P0AKopMDThDkbo{^$ zZ>Vx&^I~`>PbSL#SlBezU*Gv&tH&Q@BGtpf>2s$SMLJYRMQQ|&+Ap3o+vs~_^U-Mx z8<+%cD`BP!GI4F}!xupDp}0&7XjsUzB1>AcialhKPKbFITArkmN$(k}J-t^k>q}n- z&*&~WZ>gyCC%*Z)@~n^TJ%VFCC$)zf*^yz}vq9y}Y3)}&>?WUGhwYAH_zuK0nN3Bk zrm%x5D_~Dm+@_bL$A9WLJ6@J?qD&%m9^V(Gka?^$p22Cha15Y)0y+`PnGtT{zH|T0 zo7|STm{GA+CQG$2$4m4IQIu*t(=zXW_Ax{Hkk3rn>lo}e8;2&(PB+XT-%V&#F%_`2h`qz47Z`*C zF3U-f4cGbO5#rTwIFz=vb4K48aSrTOaCC8btnBE*z)N5#QIKxrMRbepdEC3?Z8FZx z+n{~zKH`&so*bq3AjwloX+@Dzq!ZX=7Rh5aknj*7opU+|nvp}`^4s&4n(0l~@3bRp zHU>^DFgm9<$S*O_=`BL^Hm6HU$?J0;{VI74&r02SQzTWV3gGViYrTpsxa=+=5*b~A z2W`U4%QjAo4(t#mM7RbKzPzwzM|!)Y?p#>=4~R6hUA#jO>32g`2Q{w(8BT)d)Vwbv zXs_>)9a48S>d{BH3KwD3h1EE&X`@Aiuh||sWSHG^`Y5VGxQ!dP9bX2XW@0u`yoCY} zz-)t9ArmXKK4sT$tcnr118Ny?zWM; zS7RKWpMb0KQxJqkbdz4CTiz!1={mHxp!S`X(|zc3VA5w>s9~I5W9B`W1y|@ARBf84 mSH%JA?B^oD$K5#m`4?F(XH)eh-cI*B0pH;k*Vq+s9@aofX)a%;=m<(Gk7q9c1ZmvC>9`lQhhp zpDL4u@0^ArM6ZPrNxlcsoH#X2Agk)p+Qp@Bi1xwrKaU>o51ySzFN98>>FiWRX7DtF zl!k}jA3gczhe4i2BXFOaI7>wy57b#6Yk|^UeqL{(tD(t;%a9wGGoB)0x(zh4Jj(ES zFkBe6upAfzWtC)3D^{{oCh3UiPl0nJl}tLXSmmVMk{MUJ(tAaB$f~8J(jB?_r_wPl z@?C;!9+JvIjciH3k&IV5Q(C#D3x~%i_uzDc5WWL3jwWLfsxcgxN^&?t6*lEX@#SA> zm|V__Fj6`aX%;>gg^*dOQatiywQ%a7JO`Z!Vz3t~hi{v`+erWw)-E$bHaTJSjh^x?5bUDZ3U^iBJ zwHXt{i`x$mSF(jzg%T3BLB%#?(d=5>g$vS%#W$>O6)MV7S+6B=q_aj?SR1WCaH) z5~Z}Hh*Cr+V3Qd{9y6YVzDIOU=@ih6I0RgNyK1Rex@qNBv$A3*fKvk)ozlnTmk`kD z%zX4VrE^Nj`+HyhDtQI3NY(mKCS|Mipm+W?PRV9mw&#$E4DP`DHDda0lP6{ecE}Pk zT!ReX-iXPkmTeASuw}IE&NKk9hWN}q{TBIkcZizfs09{@feT+b?+bW$T3YtOHE-HF~ z1)siy`*(oU4&bMzLwG#pS?$HCRA+szRU*{>0SdmwYbq{}*}VVv$V-Gg1wLc1_EWiDIA0@m7BGQhW7Tln)w XNhT*_^%Zuf{d<6KzeBmhJ^smmX$QRZ diff --git a/pocketoptionapi/__pycache__/global_value.cpython-310.pyc b/pocketoptionapi/__pycache__/global_value.cpython-310.pyc deleted file mode 100644 index f096c95947483de3ba96c95f2661cf6276c922f3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 429 zcmZvY%}N6?5XY14+SawD7m?!8TQ7p(Nkmjy!Gluhtt^BzncB7KCgj6*eIs8fSKq>u zvx|iuoba2E|6wLGy5G+T+QsM9a*z=6T^EN&vA9RvH9AS=H55rCowqQi7>|%P(m}e& zF_INzej@3NjFP9F!Qr`SvIfhz_(p@J)P8PESy+13^w zVmg~WkK<6RK&;t8*ChjMwLP5F2+RU^*fU#j#Y+LKkg@ZZ$KGD_m4P_eSHVbL|E_Jp z1?6L=tpv+Z&Q@YGev$ZxJ7vrYL mQN%IqgR20Sri$I$jg~>dU43ddM3>SuNn1$LWE4HVF8u|7M1*z# diff --git a/pocketoptionapi/__pycache__/global_value.cpython-311.pyc b/pocketoptionapi/__pycache__/global_value.cpython-311.pyc deleted file mode 100644 index d1418969c04740fb2f86057fd0d1729361efd540..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 576 zcmZvZ%}(4f5XYS*-LPRp`0AFA7FFtr13Umil`1Vr2=O5;y{sY`;|xo*CXQ@}Eu8We zeT1qngZ3Etv{GLHiA%+)lfX(GYU^ju{Kxi;?O#FQ6KoHE@2`#;g#4+TX>(qUrvo&8 zl9==>9HbFD{TZBHoZlf$#6xD07UCzQKQ|!#HWDE7sD-i9BU_DwldT2Y8NsENdVh5`nMLQIO>|a2$Z2Nq&fm@sQy6XE zuq2JLT9>uXvLqS^iIve?CUV%x?w)7&Ze*Mb_^RGxlIqj+1rCohj^j+$Hpiqh`S5W} zRvw#V^UIj5PrSLpoosv_lhtzVQxy&t3%~T|3$F~9i*~uxDZ;W96wNXSF;EZTA_lxx N;g;3zs;gy8@(<;hzI*@x diff --git a/pocketoptionapi/__pycache__/global_value.cpython-312.pyc b/pocketoptionapi/__pycache__/global_value.cpython-312.pyc deleted file mode 100644 index 4c4ffeb5de128e9a63230949682929b4a37b0e4a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 477 zcmZutO-sW-5Z%<8YHSQ@sr9P}o_aACk0J;n_MjGumtK}N88vk6hTX*4Tl^LN27iWU zFFA%i1@s39-gO+nM(U-t5eWZCeE6>pi}6DujF%WERF>@pFX5EAh#ofPtv^ z#$W++74ss(MAQ&Vh-HKokip8t=v2tnD5F#2V>WR?^ya;^VP>R#bIEopk z@a*we1!APP|5NE$1`+6Lzu!I6E$;!mA)Rx*qadY_^OH(}N?-~-ty{*~$O9Vqy7pJ= z`b>-_A!ujD;?QTs?@S3Q=8?99^g&V-*&F#OPBJ}pZxl%7J_Yw;%=i$b+bj3G4kKLQ zwAXdRl8chFFmUg=zyb7$@fZ%n$#m)XQTQ>36S;3 z7M`50F7)7pZ)WlnCX>l!zn>Dc_s_TKZA{45YSUPR$pzvvbb{n7A|w`3-olt*+(6n$ z2k9c4NGeIbH6wWs=_A{y4Ma31!}$7cxD)mlFmamC!Bgod*JTOZg9v-hDLSfr#T12C zt|}*W84mwb>B346AsLTvvT(qkfY0e#)hPvQwOzld7N`a0ux2`8ij^Fw6ruB%hh8K4 zrGYSLc9juq)m__ytCSC^wgN0gku|wCP=?;G&Xsm}I&q%)kYrb*`wwDwae%e^KH;au zg9Gb|M>*3)25#=PDJI$%rXGnl9$(FjEM`h$5A=np3XaV(bkCl(s1%&zKM{9?E{T&C M5+_kjgmG>C0B>uR1ONa4 diff --git a/pocketoptionapi/__pycache__/global_value.cpython-38.pyc b/pocketoptionapi/__pycache__/global_value.cpython-38.pyc deleted file mode 100644 index fe0c4c1e68fcb4497243011b191301e6229fb7c5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 440 zcmZvZ&1wQM5XZCYR_#{tQl#_^q_n4=N+~Usf|OQ4yvRa`$*94YEcvMVNPVFma_w96 z)N!fcp%Z>Hng3xvl1aOr6SS+ZV%|v!`Q9hTmSFOTxLb6BO!p*6Dw1gpV}|hrsUr=f ziJT(2B-1mIj7cwjdGEDicLOtL_zFCgj&h?la1SD$JE!Pd@PR1`TdsnWM#szlRJyU! zL(InGK|fybCGZtJsye4&t+B_QDuG&H4r}H!rkLhHrHGBcdu$y2aH<(LlrIdd;LR t%1Sqt&PrJd5BL|vb`X*+GKin2gHGsgVPO?tgQJC|T$Lqo2O^wXE9t~L&m@H%m);ht4440@bZZI+ zA(_v|qj14jz}NJs>XL#n${cs91!{m|QvbmLH`R^paGn5Yk?(&DupvHulzbSP|vc zi06PK?|{U6?3EL*z=;`esgjQ5&olPun_s=RHzXj3KYz)ul#t(OY)b&fYdH2XfFOca zWI;1Z$y*|r2sT6n*DPZqydZJ!5BQLnwg(^N>_QiF>Bdzll`HUlTXt9_$*3T}(HDUT zH(?fto`^Oi3k3t#L3?oSxu&%%wR%&RZDgcd8Z{}{7Afj@#&GN-072J;lQklBY)w9q zYt#clfR#;FM~$^F(loEjXP2c)YMm@{139PGhf7MGzB)ad9zTETOs;ILjXQ4Stox0_ zENU!YnyT$NJ(tR0>^3TV!7*z?E2~S>gU27l4B-0vE`4vMvFYdXqi*tQE=_tO?cC`) z9ZyeA)14~M&(hkbp?%emzfIyGdvd2| l$t;I0R3d3?(oM#X$@_2d`Y^t#!BLmfOYD)wFeCba{R6*?y*2;< diff --git a/pocketoptionapi/__pycache__/pocket.cpython-311.pyc b/pocketoptionapi/__pycache__/pocket.cpython-311.pyc deleted file mode 100644 index e301cf0b52bc0452ec1b63127b5a139122de3fae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11289 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μH#j)!yq)<4tGq#2~a8U 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 diff --git a/pocketoptionapi/__pycache__/stable_api.cpython-312.pyc b/pocketoptionapi/__pycache__/stable_api.cpython-312.pyc deleted file mode 100644 index 388669b532dd1b5eae0e9c36c4d74a8a7b4af326..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15716 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 O9=4ixZEzH`iT%G=MZ2#6 diff --git a/pocketoptionapi/__pycache__/stable_api.cpython-37.pyc b/pocketoptionapi/__pycache__/stable_api.cpython-37.pyc deleted file mode 100644 index ae930450789720ebf77f5470ef9e68e2dc12d4aa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12521 zcmb7KU2Gl4b>6@GfB7Sll1N#S*K!=|+6+ZmvK=dqV_BkX)iR}8a^-B5+$`@bsU`RB zUd`^6$cxJ$ffOVURS^^ciZ%&K0%;IGxG0c^qUlQkJrw9;Q1oHZhXMrx2Y0J^H(USpCi0n)Yw>()%-!c^kj@uaO8% z=z->_r|#(dZa4;>MqoBA$I{ieoRdSD8RVM#^Kn^4k5b|%^y0)5b>im}d`R^$wUVutCyS`?U#~YV-1#W> zWPIlj{I%%LWiQ@HqSl?2D7n+(u|+&Kx8>i76L%%>>c}p%w$kF8f!kaW?pwz}=0EV$ zIwyv@T6mM^DSitXRj;N?8hOo-B|Ica3$rj%P@&QSl3Dy>8iJ-5^`gOLBcrG3xYVnZ zXzR36`ZC5>T??WWH>lrtgSM9zkafd`8ZGyy66z?e(0XPSo+VnmOjr%%M>?aaew!-% z28pMU&_ZB!$K++w97|Xt2l2^e;*`(Csi1_(DT=BX!}pRH7ZZ4v#iTfdXGKhjX*{c9 zMjXa-OdJtM@f`Oi#H^Tmq&?J~N#u`-XE=Wd`QzdQ=ckZADW2v0w6MiGrJf9I?#AKdNtHaXW z$(>zTTv%+-RL~ziN%}6~7yk%}&}lMgB4`q50%-6wa2hlXmO#Nnj;&_H>`r>|9U1nHPKDrS8hxp6L?NG2cB*F(wF&h#$j zW_aJsseSM?2L_*#$UU8)VjWv5IV>Pr{byfEqbA4DBL2;4KTE-c!o zKR$c*EA}-%Y;W3|Uw*Cr+WDG&vDFH^+uq9iesbp3bFVL)d(A%m{(HARxHfMG{)T72 z>oqo_ntkc6jGEq=*IvUSoICgG8w+R8y{yqV zKDh1_;&$BfLgD0-yV7%oGa0v6;)e8Byn4e8Mc_GedrRtW5Y&mBGd}p#j3U81F*W@# zabBeDBm6Z#!FG#VQBrToXw^^Z?rP%6dh9}52sd$^Q&bT>2Z`VG>Tx15uzKLfNj>qJ zEmi0A=hyKzTfTHj&8FiwmoT{N%k}q`E?!>xaPWTV`t|p(E=iKi)P$N!3vRp>HvGsL zC*t0HEC|$h)JiQ%YbGYl3s#ZFzQ7*#n$cbjQLNYf5F@I;2%@o#MAMJpKR^6$RO!tq z8+que&f(!*v(RhbAjzOVdeV>g4M}8ITZi^qC#?x|dN=X*Ip`+z4X_)gEDz=1gQmgkX6zC7;pz^w9WvRj&>)V~;R%K;;od z*Pjcr3|&$?T9;H3>_BFAZ9JU(U2HW^I{8KvZTQ0mNZFp4mkXj7h1XC& zCgZ33z$8$`>Ctk9C75MvnUIMa#H_JW!xu2a zEP0hkg6lf(9F*Cho>o`dTXj|Li@kC;L6gVTRh1iPp)9vCt4|u8<0He_Q~BT_&FmXe z7em8Lo6GVgd}F3FhUGj8GDhW>=p*48mGDGQ?dh`ai`%Ht+ZE4#9($-eBBQiMyeEi5 z+7p;yW5?XpcJ;yDuq2t6)RgT|O=+PKg`r1q@W72y!LF9s6goqk?U92)r9Mk0YLg0K zmi`5qt)CMTlBW_d1=g&coMJ7>3!qC~3ya7Zl*8A8FgHqabMrWA7wLUhzpQvv>gK5KZ7p21 zv}9tVEdN>8whdkDOo2|mYy4RIDf4z45-iMZYa&vQae4`jlQpO8-T3R9U>ueemRyBodJwKlwCl$S92LmMdMG%rHD5 zI2Cr_l61fdKKW(1&~Nuq;s<`mwXq7G4BaLq#;5pn?_N)0`LSQKggGpzr|UW z7?Y(}1#4Ht(H;R)XWzGYI4) z$|)o@i^t@Qz3U}6+$~JrWq?mea179x3nEv_ImW-kt+~FhA6i=BWp>*tsby zEE2(~IjG;XtOiQo|B||=HPFmGN@h@}uVknmNhnJvfwIlnF897skh3J8LMKCi&$z8^ zo(5MIB=jVy9g@bb2E=tL$q5th)|~bbmhIjt$SQvY|GDM0KmRlO``fQ6Ke2k9pq71! zb+L-qMduR?C?+tk znN@upPD@pvGCQOAXMd{(O$hyw*!9827a%a?c&{@C<|pDI+qtb*x;c7V*D+=15fISG zNvHR2?oBP6BsX8Z5mbGXH1#V0112=}CNy;(U|=QzY{4Yv2@Wu>B3+Gd-_G~p0Dwom zASfl^qV1G`$&IerwYs_89AHVAAj@`PGf495MWF%a{0cd=pxlk@d$%wP8?cf4p)QT2 zLf@0>Lqq;n%WIxi^oOdh2V^V%VEPtdxFc%ffckv158$??WoacO9>Q_0R~X8A?nII?u%G$LTSUvy&X8?R5AcPtoPGG5Us6&A4y`bgfQD> zq#o0}^kOjSA*2zY0;&|@Mfoljo7mH}qH-MaZZm3!Nt$;X3DAVY z&nR>%19u*{M0)oCwAs?61-3=?WRE<{_d)t^@QVrUa7+;D%^(prW&kZF@q~J@W{je4 z=rj0D!AqYqND+=3oyie3h%t;9B+@aw<^Kv9pn46mL(*(+u57`D0?Nm>-!{7@Np#nQ zWSmz92&%j!&)*;^uIj)YdInb&jjjRtGB z)dKcKal?{-Bub$n$}oRi*0o5Qm`g=LmQc@*R9oVf+90wa0Bz7(|s!AMtnh|K_;*sOnmUsfX|HtC-Q#FW!QTS;PH z%AC9k`6xHiM1KxqE3wx(aWzB$))&K+dep@N>@bOtT%hCwN?7bjetIUlI#E`$YOO zVuDWJjFW-vLa86aZw7LbAQC`fdJ5j?@)*7%uEgORFxhOSdF)I-63z@wA{QYNCZZ6F zB=&`yN`Xs3G1UA|5KE7Ei5Hljs^)a{gHtr3%~;X?@-e})VF?}AUex_ z8gLWv#!DpB5b`{pMi-vfuCZMRt6g)u(EGBcZ5My2Z_XxW7hc#!UHNu}Ov}A*evBU!`FOXe0`y`EZbh+M zlvhwPjjcH|2eWeT3hGSkOm?CAx2xUi`XLJ6Ki1c$D184IWy!?)bQeK=v_KgDu|fGa zHPoCz%_HL2V_*Zk9o{)2p4rvA)%s!7I6kNC93>Y;%#z{TnZ*i?b;p?MvBww>MtOoq zId+qk4VRpx62^~YZk}ylfu!~wkq@B>$PsBi`4ub-0?o1ALevZ)Q5@AMmm^w9ybve^ zY90>BT?A#H{8IW2<(%C2AZzv(++Ea`5!_^V;N@KQ?zg=F%Ay_GmtiDUB|IJ8?;%8+ zycRe<@N3aM(73c)__4nfw40%8zer;K;zGI4>;-bJnzk|(7wn}#WqVE+e3~{Kn;yT! z7_&EEC?lXY91~y%40?#UNqhvGIoEK7%Z+U>v}13r9f~O4N2fuii^+sfe%16OH|^EN zsCt}$1`|{>3rBDs1Kzw~zp=QuXn)l{`|4sgxtNi&cGDkn6f=&Y77zfU`QYv{Mpyk1 z<7JN%)5a^nMS`GLoQ5SR!!KbQC@A?}Q!Hh<$NNoVREZ#9yh4OoDAqSJ<09)^PI9J+V~ zght9qi9Hqcar)*ppj`lxdD~6slmNr(?ZtYjlN=y)?VH@q!2gxm+n z8!KD4@UVnLltqsz+XnW-v*3rV0I@8`I2&`krr~ueSFSE!z4_kK<>#Jzu2aIGJPx|l z<{0fuGG~(FgK18>;hLx5U6pohB)9kwg-vN{v5hQ)+un5ZkQtoiy-jR)p?rO^xoJL* zB%HX^Xo<8$+xQA?>I^kbD?oo3yAPwanO2gBI^*EGQyP@#)aTqPL_#`6G=cfU z(UKM24H~s^285DUM1CEKQyvI?n&YkJjPau<#wy%jq+LN4)2UD-D;rU&*X27zilP%Z zOl<<$`VCb6r^+7y18S)%9ftdU^~iWJ5XMsuW+Pb z=hpLsQ`pDWP62iWU{+LMZ;Es(`v}er-lx&RTrc6w)`AT~jrB6wweP{V<+n;^-4Kz1 zrI~H#hb_$#E*ZegsCZctK~e3~EI81F89_woK2Ad@AcokivK%aOWH4IL4{khf_Ye}d zQ0fqXHX!BMP9pRJ&4utAmNJ^}+YiKq-JsEiuB3iQ>q8Li>+fB=WS_o{P{6$Xp1*c? z9$M_dymGp0O1rZ5R-_l~Yi(~WnjdB}06XOTkWo`S!`UGdinG^fsCTw9$|UZ$YUCf$ zl(-kjR)W7_OAECPnB554TcF=`F)j`bW_Dl;rq@G%`z;jA8|*p3%M11melmw@C~=>) z3jp5m5H8psg@`WqS+}WMEC;$lka6FfxofiB!kA(CVYoAz{goLpm}w!?Lh@sXu=J3S zj%!3@>0`NqcM4{awsy>R(!hO)Oo7NFC`(OHcgDF3>~)x3;n8^%(MfZwzKoOJn+eOB z<>h^R&hl_N0Gw-9YEjH5weDh}Q!|Jj$n&T)Vi!yluw5Wc5Ae=NxCh`32Jk!zfcbBt z#zFRBqi-M1jo1f;kYZNF^=wA=8wxfK%TNCoe`x^=3s;LQLq3A%6(+T+T&Hd~C<&3^ zo=GBGlqsxXYFaB>j_Izg$tD%$T-+Ymk}mR4`?zuA6i7SM)uWueh*G6%4YBIvNDbeI z8$}PUvS(=N%t8F)vV#_rSEE&r7_-Jnvkc#w{;TE@cu(-6j6Da-2T>W(cck-J-v!91 zLo|x|c8zs7-Gs-5`Pc+LR)KvB@44)q@`lJWdN#;k5Cy`;&~~uyJNZnn;pPeHEQ%Pj zuCovtV)waLZvPsc_xHtz*w%okz+A)13kd4TE-*5~y&GG}-3XdP0Lel=Nd4zfe$?y#l!kmdTjGmwFUbkZcsoo&tnRtk5RdH?4uLtGJh3_ z5z{fE*J!jD1j@2;vvG*&1jN6OQiSboe+`%)TAYAzZvuJRm_Ej^1&sv!1AhX6D3}JE zvOvEo5Yuy6#6U~)G-EYZqKFO$DY}8(!V?5uaoB@?_j3GMdjcv|Dk?+ z_~d|{OAGKB!aN|rCiNDMEx!eef+KI1H40{vD=8l!shJ2G;?f(=O05kzmiz_afh?!W z>#8z=@nwXs2S%8zlruptTs;%i1Rkt1zpD_EfP&wvtGIC%1ht$~QD#B;W9;b^u(#bh zOmJmL56bUS&*X-xghF=d_^4?~$o5X+gEn1;140L^RUF;S=$&T{#2u>Vk&8qo>J`PiDi?~rkb)(G@M z6UB^j$>|(umt-J0!%bL;0PfvQ#q}uT%zRnD4~-Ld7BdQMrBONcPM#C3C*?q9wh@=;T#E!JcNXJ z;!_cdbQez)amfPzRR2^jwW{OA=Zhz*h4Rhn^HsZOa%)5v=(dvlDkU!< z0fOxC)on?uE5AlX)2-2~;0w51$=58MxxHUVUy>z`ozfs%xPAkN)Hv(pK*R?`vZzk1 z5Ggpmm&EY>ZFLe=fhU3c7IgbF=1b%fFb<&$_?NLpEncUEg#tB5-tEOLv31T#4V77*vMK!9v6Mkaqj4mt1?AcsK6F*hTC^Lth7 z?~#E`H&`r^#j00T@BQAdUh&S%Oi97-?SH@9aL+2rf6~S1PshcFIN}{7OkrxE*z&B} zDz3FaYw5PG(mg#eS~)u>@@kN86>Kxh7ww{Ww`7;Zxonrkxzd`kXG9qzsJ3S9S&`2n zKWEQn?;fxZdio>9o@aShc&yk9%w$EJ5B*GGC02f{u<|3#KFrixO0Dt?QYx!8HJxdC zi4#P1H|R#8ha0muqxy!I+-N6$6kfP_^{JYcnb&YTLBjlcf)Y9A-S2pHjjG6gls9nD zD8z3f`3**3L*sLa@*DLLT1=E*E5BA78q=8m*rYp5fgHx_GZm%lIL3Zd&peK}iUf6_ zwneH*!#mn4FW@c&DTiL1c+4TWo+?kZR1H%j*=l>dhyveUzqoSuPVDjc?#H3uyqDa) z!_a&1PRsSfyKT|)Nc7xo`*-8ST?;%1+2!_DYMu|=)*5r)KZeTx2Zz#q zX+(hK^I{a^_mPq1IE@G=wGr~j+zefsNEUI#c_fNzs-`CHO^pK$&c%MgL|MnUJyln0 zT3Tra(V81Ln{LqY(gLz>SeN~ljnN2jYAh|&3}*#ip}NGxX`IG)szDm~52?^UBJmVb zU0Y+St&53MY=apr2c^noN|w)*tRPj)Hd%$u;CYc%*(}Z_HpdR&T=pvLAe(=zJW}l$ zk*~7D>+Wus7J7 zcq+Hyp>i)XSka?y?ko<)Rewb|Hat;IWkP84pQf+W7H|?5Osz+LY*M- zoAW)zQF>~kIvQI*US|qYqe*E_q1up?~ISCN|$cdNR zZ$U-<58p%bCv7C(d_}oGR*|d}6<>LBTxsHc{Ql#EXQU0B^sHMVV|nL7(+iWXe?98@ zLExS|yL`$z@!9FqKen#`MomoHqp{K@e8;*A?0U%kx9=u#aplNQ`~E3EsG zU8TysO-u#AIcnPmrLcP=_JRh|*k_Y=ZaR)1qA$*IR2TmciJ~4>HD&DoU0%zpgTGdx zyoM6r<@G#psC#JCa+dow)=5A5Kg!b~Y=?BSr>p~+)`87@4wp5Zzlu{@lw*hfVER11 zi2ru4fj{?8M{8$Vm)FcY8qI^uQNWo-x7smt~DK>?V1Hn$=RXN8@Jqjj+Lq7^p z;onb;-!r( zNO(?dQJvGe$HuxeQTX}YTA1oq58Z?R&{z2qo(aMEGUZNEB0TEQtsb0c$nn{26zeO( zE8iy+%1-Gi&5+Ook}FYmpb6{Rj=rnxszmVmx?u3v@I2LdCzNJZs7GPw)subIwG3Gn6E^QR25% zWm{8~?qZT>dQbZ+<*&6z=)|YWFOVao5R4dw?T2&X-?!uTBW|~q*;j!Pq(tm*4G{b_sR~ZmpTqzqS zKSozfAMkcz9ak_C(r+Ig3hKvB){r;qqlT6{E^3jAG=Plae9UH5Y5m2i%TrMx1@ zJx}KK9?VOJd3^!%%EP=~NQ|8v3@lFuMkq02U?}lLeqdlql6MM>Qj==kDZ}r%JmSs#QGr@vNk~`_2nMr9Ju}RttNUM}oJnPOpk_?j)f$+wT5&bi;R*OV7{Trv z(xQmd_%4hZ76dcia#~o4`i@+stynef$|^sE@@dW^3+%pqC)^052cab{t*Bn_aPBdS zbzoSc1}nk}Q82Ur`tmR7_fH?xW~FbMn{2?Y(w(?duX}Oa=mZ?3T1g8*)NGYZvb)FD^hw5|CSp{>YVr%4tPg z)8$$Lg0|Xe9>(cM%wC{D6t}PtV^N4nV$oDD0+-fgDEZH*NU~g`GAZYv(CV}H5sV+! z$x2kNE;2%K-7&hK(0M1<15EIQh?2kxWYw!1fcElB8tBoZX*1?FcNqQEoC0+>D2Ydy2yN8wd-tV|ymqc#U!wg$n z7L3V)i2CfW(DOF-ua2P+ho7DAzj2ZAILbF*6R7S3%Pf?EciR_I#C zNx6|QL-K^M6B-+Ko)>XTo2K0_2C4naD>p5 z|Hi>*k#yf0!#pB=G6WS*M|=^J);GK@xpJg7a6?<$x{Z^;c+^hSn6e#Enx|@7@WXcC z);(K09Sh!H_iBs$1U0;hB+YR*Y9!0a$7va~1-ZA z*_hJ}t*V#w1@NG%TGbbI@SyQk^2p4O`MWVb^c9+uYh)QKVR}v&97v@`(M zEnrko?*K@s9fCwmwRPj8YZt8(HxLnCvOdC2&XRR4daz_&B=S^~&?BHtR=sRp>v+v* zX;jPr;t(Y##FsLIfs=nh96B0lT|lt(7t@eWq@9MuT_HyT`9|FkFb&MF9(1-)f6GO` zM2sN=$u0OL5A99EH?SYhju_91byJ*$gt9B~K(rO`L6FuJ>rQBmh;C7{m<}|9XvNf+ zxlP__qt9@Q@M>9|gP9rgCTSr<7=8^Z%zuFdP=(+Uxxtv$wLD;;hp%kvF9&hp^G;Bqx1HLyIxKHfb{bgBo@ z1KyBM)FZH>lCb*^S%}tcp59D8hlaL^PW(?#diHHT*I@!2P zqrE`g)o0(~XBRNNSOWmw?RxqHXxtNwf(wg9aiQVzF1Nbp8#Z z3{2(|SUjj5xXLK5fqCrhf?EojbaN}Y7Xefl7$8)a(Bbobm!%i^e90O?Lcp8knnZfA zPlGT5@lG&HnDE}$6tF~*%;r68Vb%A(ZctnGs6}R-a8SA-b?NioO`s4e5@Csq^rD); zZ?%07w0H|_wdsan$aM;KQWH=J7&T&)#G6422dLYJAlw3o(yBDf-Xa8$k}V-1>Qi@z z)pgo|4+xFoQ7Gn~@b$9TvUP!Z1lmg&0`8+%t`+-e1Tg0}JnmsQ#vqS+i%#HP)^FiA zV8@Vp6L;e%wEQNxG60?kWPbp~u!;K7hb>xRM+oFv2%NxcAd(B{mE@&Q_1O@>#V}(w z)}koz;8=jjFd;EOkQD)Cv^y#B^W5<{5#1%pHZ`N9KHJI~VYf{C6Ni{C~JoHhda3xXwRJBZu8{fiA##C@J}1 z2sxs`fEY|U9-*WCr5LQvf<T{4{KDn=7 zjP@PmGYSW-)`}zU+M&|3v|@8I?M=KmbJPuJP9SQgFm9uPjr$;|PChu_GzIn4m2}$4N_$(~4LJCgr3R z$GH!;B%j!2$AQArKDy)ZH|S-tK(A5mZAwH~_B7?*r9^D&Rj}#>$7p$=7fi0u=jkB@ z<@v{y&;p+~Dd|x1kP;eJP8@1vyodWy6iKE1xtJP6pjI=FnL7T4SrF%01a2!h^5(1N zOLQ(>K>8YvS4^GL+qENN@0g!M9`v#+zE|TkhxUAX{L{B(e2gMKOR|*Vp3S9 zJuBpO=rctzZQ{Egu}vp>x8I=Qn>{(L?^7>h3O3WXcC_%XAlOZRej C9WopM diff --git a/pocketoptionapi/__pycache__/stable_api.cpython-39.pyc b/pocketoptionapi/__pycache__/stable_api.cpython-39.pyc deleted file mode 100644 index 9e457df1f75ae51072445abddfd6f3737543a38c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9776 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 bOa3NL|HV^>yw*ARrtlko2+WHnxLEi`. - """ - 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 7ef767b06a2b6a11ec7ab6f270b67d152312c780..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 175 zcmd1j<>g`kg34X{Q$h4&5P=LBfgA@QE@lA|DGb33nv8xc8Hzx{2;x_eerR!OQL%nm zW_o^kVor8yk-kf6adt_5fxff9i>rP>esXqdiGM*!W`3SyfTw-|n4b^kCl+MtCnW;q k^HTKV<1_OzOXB183My}L*yQG?l;)(`f$S<~0un3?0HRVXIsgCw diff --git a/pocketoptionapi/backend/__pycache__/__init__.cpython-311.pyc b/pocketoptionapi/backend/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 026da60c65cc7528936a7bc7354c02ffc9a1c350..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 178 zcmZ3^%ge<81U%-iQ$h4&5CH>>P{wCAAY(d13PUi1CZpdL(=vg`kf|gzTQ$h4&5P=LBfgA@QE@lA|DGb33nv8xc8Hzx{2;x_$erR!OQL%nm zW_o^kVor8yk-kf6adt_5fxff9i>rP>esXqdiGM*!W`3SyfTw-|n4b^kCl+MtCnW;q n^HTK7i}mB$5-bb=J5McP 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 e913397c8c7c0fa19f5a0745f2fbfa198493a0c9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 181 zcmZ3^%ge<81U%-iQ$h4&5CH>>P{wCAAY(d13PUi1CZpdL(=v!;4&>j 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 20be7041429e2f02df64e093b8f0dd792aec2122..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1366 zcmZ`&%}(1u5Z<+&coUpZKy48xBu?%DzW}O0p{jZbNKsYEmzD7@$l%!Su0zx)H@Nm4 z+9MB9^*wy;sb_9g51m0nRzJy;Q668&a*!r6D`Pyo5sY2X0jPchW=`jXlaiiW zb8=2VZBPfa1=<$$D|qe-0!Ldz->KX@<9Q*ostdtbY8{=)ia{)u%9V-YG}qE+l|4~u z)tSl@ZP0|?J+OnKP}nl%plP9LgQ={k_N~g%GCfs@@`k7taDPU@M=g~OE|ar-8l@Lf z1&2~!lz9;hjt<`i$0os1Q6_nIaQq=C%s4mWs7Qj72-=Z}V5)=I^kldAQZdexEGaoh z5V{YhO&N9WKJV{r^B!;A$lYc`jMy8&7`uucz4gG9WNuwi3N#pz8#=t9RjW`*R{FL= z5~&WD%DroysjoJ;*E9r!TRhUEv#n(ei>^P5LSZdx70nAUkMg$6H7#ew8!+oPAb47L zbGtEisu7Hr>K3%A8@Mv-@9gYs8!m`#2x~0Pq6gucndG{R%8BN2E@arLe~tCoGDPIZ zI;eUUqS^X{>SmSMpLSpVlyt_6u56HZ0kMUmDNs)7i!{2ACiE z9prQtjV0$*m-BHhCMm`q=btB0TF?)sOMjea^?(6(+?Vm_is~lrd)G=hESS{{USB8`S^+ 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 a235fbc1fcbdd9fb68b846b621a6494ee01e2af1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3837 zcmb^!U2hx5aqo*C;!&1Pv@BP)L|LY6Ix*=;vaHH>Q&zD9B?WCjO$!Ny37U7M&Z3U& z-l?=0st_QjPzpUrfeNWe0uV*uxNr*eFF1KFaC8ZTI6y#vAVrP7(2xrVe(LNUdFD|{ z>K55u&du)5&dl!4&I~{G``Zwdd*lC-`aKAJO*)Oj){(t80NF+&5~&QDwZ7CW1${b0 zFEFz#WsPyO9F5*YB6AOk?0p)chw!t?tV^V(QJ8x|y74e&xNh$lgPEApNCQugapV`$?dfc4UeYErvZQE+3rpG}R^q7! zN5Y=q4E**20Jc$)Dxw5nRuK_!qKIPd>1)%|Q#aya+F(^F^DD!xN~$Vn6_qdu2A^M# zzN1Q5js8YXXYwjj?5xMn$^F7MUV-ALKu@ zby#hpqR@Kp7)S^6fa)}ZRr&=t3Mrj~=rz93aF$#_z1*=S zi!LV@=NZhv-j})Ga*D_L=BYfgcDDYvusi-U!*v78X;~?B&S_dsorr?bL^PZfr6?Go zU%Cr;VoriSRtx=;Sw)dj#Jqx;tg1yKksy{*^+6^5gahs1A++VC+p1}Ms^P9r8=Y@U zOSiL0EKVt!gz;idGg#@atQp>GcT=$ zNmu&@c2cD?4}SR9RJm`W(l?>^9NuaSIyds*c~;h;U0bdVv+j1Cs3FD=G!3xZadacO>DlsZcmUPAFO@sSD;?u{$M|k{ z@78>2U{l#r^p4a2wSiLmH8-#&-(I*!^)0yE_xfUI(I-r-oB7lgquIZ71NMj&VDM3Q zTP)~)6l4fKND=(BiJ$G6^fRB+UIKmNlOE;^4+nUv$r&JG_on0f2RLWT@ijX=a8r(l z-aIF^%*~sz0uO1nQ$t82{OhHa7m0rF(Z?Ip$SH|L#A(#bt93mE5vbH8%Vq@e)5#0J z>EOeZnoq!91`*e>SCM<6+lNSWeM~#&K|Wn-+Mmq`iEbyQ)3GT(g#hBIpVsy7K{N0F zpjq2@Y4)w>>2t2azx*Dw=3idxBDzCdq*sBdN zIXa{Zm#tRjuT=OeI)9}q^lnE!h?D|lVW=Vu>HOiX>JRKNzrVG1iwHi*^`b72R_4bl z{Fu&PditiQh>E26+YyD$gAg#>DDtt`mV-5a+PfnDD zNJWU~{2>i^H7f;rK?4`>y=00qtuY06VHa|VrdPfRh~tDiNr!yT+9%hPb7Z5n9Nu+m?w(b zn&b1({WCHdSx<#{a!deuTJ@@IM>6$b=qKz=0t(djOUZgU5ts@b!Qh) zT_@#`1C?@5q_z?#nut{DA%8_rYqV0VtvL16nBg_Zo;6pMm$|BH^90J-THvtXHVW5;- zjo9sXl!ZAhb5Aj@=5vZ_TwO{VsZ6kDnK1C!-~U&@RZ2pMNWQoffF6uH#Rz#EA@ETY%l+Vt5FGXc>3KOT9&b)a}7vSr+Q`?g#7iBAzul(4Z%l z#4UcOo`%KG?NBD*eW6rwbo6Xm<HCC85Vg&*qgiC9kI4-io5@=`2W! z%?0jE6L~G05bdz0N?Jit?Pg$HC7S}TFs3oiW%8P?6#M3kG&6E)Hny%T=-fms!yR&B zVj|ID2Ul6%Zg*@;=|WD*sk)I?Gm0ICm9j9=j${?%Ch!W_l_cDnB-u?U#StBZxMf?s zk-9c(N3LmXC2bfWYPpunE1#uRIS-vE=pBff)3WSIBL2qgs2?*|3I&F~%FrDddK*Jm zvIFb7E#}lkEfKOsUCA#p^dW{$VMiAIrK5|mw6x@`t>cx&u)MjO{8Cq#o?Odiwe@uV zp2Cuu?9%eRwFMREf||U|5t=O+IZeHC`$p0MM&o@zC%KS@h*ss~x}MBHKqn=@D`!a3 zMBzIG$LSRN{Bs<{{)8vR-ROS;*&vnn&Q0+Vt;8>EiWZISb@p4GXMUsQ&Pl6t5+p4Z z8hzCAlJ@P=zF+SA{B@a*S#->#V|yLFR>!FM-mP*+%IZjMM*e9c-Gf%w*wahpu4$`l zdNW#~9WUw7E*;uRl(tq&9nZTflOMh4tDK*HF)ukOR~NL{kS6vLC949sI< z_^FCOx-JENBEK{w2p;yn)|Qu&(Xe0Sz|k{#Xa!Tn$So&)W@5#6zfdwc@KZUKj=AwN zG!uIkv_l1!Qw@gU&oE~sg6uT%-9YTHtE%JH!GTymiqI#E14rHQK`?fKj(!=)2HB&n zW?THp86Z&0bi|?~CLM7v!vS#S_b#(%25OmJwCF|Ct85TV91Y|*AgKqk^nV~c27C@- zAXRL=t{DIsPb)Jq0B0DM?2VKi6m@|%bv&J_9vi4g!m>etx!9kDC;9{!S7LUgv zsY1GyW55>QzpAOK0x4Zn-Qn?AMsr8>801%DRhKfFte|7YAWOxJCB<{gSWa&MI=r4{ zDrDk>SmT-}`GQi7F+1W6t>{@DF#&&d9TrpPv6sLnp_}QS0&(4SpiBoXI%v{C_ab_C zX|LIL-~9Y8QmE{Ehbhws7JXoP)wz<|XvOQ`SMn7w^$y$k@$hyr-*}B)w4a=X2NA9S zpmwn?hMl1yHUw)Y0tfKvxI+&fbedxCG4YJAY#bEyb3j~lhRSr#Yqx_SE_NMtE)bjnJ-rVIJVOv`;g=MzxJ>&k+HZQ5AAya7RwBaiYsqew zq!mqGh1XTGqmuOPYMOtnND>=%JmC`{ct#vkJb$8dG9>ud*MMyJziQw_=#wg` zCnJH-r7H0W4TL_#u1lc%gMfXX!pH~?xP%I!fhsvnoGsxdAc=KC!=gxVfK4L7sNgC8 u4ZY-?`ZB{zg&6BMfmDNnAXLZ+)2kKIw!wc@aYTSc50h6o_WB3`U+q8fJkSyV 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 38cc8ac6a45b486c22670f89666989d45d1afb80..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7015 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 z�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 diff --git a/pocketoptionapi/ws/__pycache__/client.cpython-37.pyc b/pocketoptionapi/ws/__pycache__/client.cpython-37.pyc deleted file mode 100644 index b841419314f0405418161925aee0db9e2f86c249..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7299 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 diff --git a/pocketoptionapi/ws/__pycache__/client.cpython-38.pyc b/pocketoptionapi/ws/__pycache__/client.cpython-38.pyc deleted file mode 100644 index 9049a4926bae59b9c2a76e8239ea0aaf4d8b3f83..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6834 zcmai3O>i8?b)LW7on0&z3lIbW5TZs9<#1&Z1WAxqAlZ?G9|;k)UD@OXcy(XWS8XIY?tjy&O6Yq+ErQCeX~7i56ZgX*V;q&kgON{ zdVAO&Rux`69N}kPs`e-!I#BGhd>G{zE3(q3ihYimtc>?KtFQsQCq7VEl?{HXut7d~ zpxft}x~4R0Ut#@9v!SOZb7LNRZJw5?WZaM4)TC+>2E3uB#k)%%tgJRsGuJ^~+Yef( z*L(FX5e8nuMT-XP|LJJ_JDz9?MGR3LsHzgHhZ@sfqQ>-CcMN7=F@@x{k9oWmGAj

b7(OSE=u&}hYwtVk)a{m4iTg&%&5L>M< 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 diff --git a/pocketoptionapi/ws/__pycache__/client.cpython-39.pyc b/pocketoptionapi/ws/__pycache__/client.cpython-39.pyc deleted file mode 100644 index 448012b035fdccc6adbac29bf45fd640e5209013..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5717 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. - """ - 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 f254efbc744b980a1a1a889f0f2f902045b85007..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1146 zcmZ`&O>fgc5M6&HaV-KpAcVwa#R16|OGuoes-i6);y{Tgrzq0a@orid$LsDolvufu z9|A}I(q8!&JuzcDZV1gvyP4hb%$s>{BKv)xzoKlC7KX}<(E?i#2)=%N!WJKN* zZgG1>xGk(7&dBD@8429-)kn$asi0G-=p@pD4rTmR6!cSGBr>a?Zp1{_ukmG+i8PEM z=RXT7sL`H-A;`!AM4MY9hXc-vj9l*V4)3n4QHS>cch9)T09J-4X{7Zo@c(h`4OFyo zpTO2g#?o3623AL5m;fFRB zQyk<~@3JBUvV^#|0uIl^Hs&<2j04{UwsEycrwWVwO%y6}`|izPJX`>cYBp5zO2ozZ z?e}Pwr-B{L3we>sY`ldxzR_dk9f;Rslqk#>#$znWl7g{4>>d&PPT5CJ!?nkd7$MgS z?GEgUu6;0;XfHZsQ{K8?d%G)!MajL}iTc}U7NN~S>yfHlcN{k9(gezw>5CmP;MpueiDIrYy<6p`Ivsu%I+vLi8>$6% zrIUH0fdbZ+$C{YHRXF3u#TJYM$i}~Gw$ipfkYeS*ffQH5?EB@jN|^gI!?>s(d>eF8 zR7}s9@fn*{|4XnRFm^qU(#F$cjLVoY)m;N^fTPB$s=xvZZyHPSh--Q7!=A#|)4)-W c;Ma8J1m~}Myf26G4(b{XF-&7Nmppd<0{*is+yDRo 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 8a9c9c14459800c0ec878690af86c1a2a6caec77..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1392 zcmaJ>&rj4)5Pq-iF6#zd5;t%#G4Euu8hbK^G=xME4KX5d^|ER5DD49ZyY2E`S7gJ1 z0|yS=xq9+Pym<7_Kx35j>OZh%J#liT?G{{FU(I-U7u=9~82#6+3Ey8Yc=V>%%} z@i170OzGbQ<(PP+K{WE1c=UjHnxJpt{f}^`tD$*%oz#r(-KU|~X$rO$O1A1K!IndJ zOGNBhJMzOIeY7K1Rr=K3a01aZ-9Z@Kwd~Mmpd1rH8WgZKk2Z8qJ0K0iD|*HOZ4|r$ zAdbbx1Ayt8^G!#oQLz6a4p0ybb*!Xc1#wLF=pNyqrOTs$qIGx6EL&FFkxq*_ZQo)q zHU#qn6*++`Sh&V47qhTd(xTIv?=LUrz8GS*ozxyC4S^^o-a`&^D`xJqRHLyD@6@!| zP@=gekr;(JGj>*=x9pcnNM%0>{Pm50BnJb0V^EO&`)|sr3;)V7Zur6j4V8YV@dN*(vk6Q0*{K;sW(GEg?Ie zQqTgPr^hp&njwqO!PvkOVjYm<@_Dfe>A-=LW1z@!5Z&tupO;6QgcsBe5WsGgOjeIx z|EQQ>E9U#?Q~mR;FO`LFm4%big2X9+nUwhFs11a@_{6A($sKHBnr$}&4bv_uTx{@#F*)o3Wez&$;H)j;SueT|H(8yMqR4u z@*3=MFQxrijmxuR$C6?bK H>HV%6u+~bG#E-Nh}r>xZZ(6wpbAA%B|t4=pi~r=ELYdQv~C?cy>pr->cEhp z42ZG+0*Hl?m6-*^fa(?gfQpu-6Ytqh8U^|!zk7G@>-RqVWqR5qK)*hBnoK9;CtikB zDumNJARG~&G>AqX6Q2%8o%^MdnwAFMtZ`C^EAJF7d+ngLO4{3aY0*X@zdibYc$Y%DD`ln9#=hoKNLW9LiG zOC_Xo_JYm0>xMf*IuoIsiF8g^IoNXOxpHticDt8WI1i#A;k-YMy|jndE12pkuvg^7 zB-Qyt<4xrhuEaBt7leSa?$tqn?vd@GX6)Q9c4X`c`>Y-4!|hok7VV#}iO6SC>~@4o zSQ1a{4ivj5BpXfBHb;Fb=2E!wz^_fJR3cgAwnRR+Q4E%EV5Yigdl4=S07gq~Ug86q zaR^c*9T}<17o$L@?B_zY<4B3IL0eij+H_7q3-|~gFMKRRmZ5p6fhD9mWKPYCVigVq z*Go=;AaQ2>TiGoCA8i&4)NNoe*(#Z>zTJPl|6|7fHe;JLxiOloSh~CkXIhRowuP5uezFQL7Xi;J^&H5eoC5`gPoZ$4Q%a9X?W{aU Nm+9fcAA)D*_ZR*uN9+Iq 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 3c4fbb0891064f1905603b5c9f0b896ffcf8d3e7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1117 zcmZ`&O>fjN5OqFwH(jxU6GGx*amytqPEo0#MM4}X6$K7aBsUv-+qBs@wX;->_Qw7Y zI3oU0uKbIN12axGT`gTpV^3_)yqWi$!^vb!VEp|Z&!|JlAN*Jk7o68%`WZ}|MC2Xe z7PnV~+rs+pL^gL$iSO1gKS;jF1)WJnr->HyM5b4wq94mDlSR9ABc{6DO)ru{HI|9t@`d%6r3t?y%4&hmE6)DO8q7j?NtcIeV-k#T`0?VxP7E6MT=xETHgB{ zw0%A1=$^JNU7kT1bA7%e22M6hP@-C>Lhlx~t4;@>gf3;F#fDJ6ZKP?QYM_Ai<*_ct zcNISA#>EyI2aucbeX~Z+c0!7k2NP1fgc5M6&HaV;V^AtWv)D;1c{A^=&xS*nzS(^CZ1ir{*?2@OwegA=_FQyp61E5DCy^-O!KTcT8fEkPLsuv}Ei6si-;XRYN4$-*_}h5Pvh! z?!m0NdjiIaT#+9JFlerTWDDPNle62vyQD-+39(#5W;nr;+b=477~5p)BRW zu1l4YX7OA!igY<>^S)}=J)v#7Sqf#$)y19|@NAbLMY)if+An!qo$|j3RpglxTM~L^ zEt-C+fCAQ)$J!XrmiXYcjV)*kkhOc?Y%O%NAjQ&w1u1TXajWOGFb`*jw<8B|*XyIG zn4U51GB&S&Qm`K}cC(0Qt!Ka(&lAREe*?4yjvC9l0s}1EDZJ(ww(Sfgo}GA>d<PBqqFWodT*#H0l 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 6e0aec94f72b2c494bdfb56f54e9e071bf522c92..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1138 zcmZ`&OK;Oa5MDo$xE2wd5E7Ra2P9uCA#t**iarpk1R-iUS&_DmcSBtq+ue1jV&z7D z2psuKzVa`6VrK2QAv7!PdiF8%&3xZD8V+3o<8}3BMlC}A;AVT+uz3ely?}+2kbEHA z;O3feQy4$2(B#%7@$BmDr<^ZRL1($7(?|(A&f{;Qq@Rm2$+PC@PE1vE8ed15Nc|XM z{xcwh2JICXf`kSjn%oF24mfKP+T7ti-d`JGj}HL%pter{tnyFONU43`|KpllsA%In zg{hE?m9ZiWs)YDvwRCAaf+CX9oJK_w&@b15CYdUuEEY7M(I7_r;HoLQsrh((-uYsP z!$Q}dYeFE3k8{W2=*sVWMxIey@WwN1TZwcgG0ERVq9XSvXTfB=1RCXhEb|)?mlF^` zr0nDQ=>74JtQ`xS1pQ~rfeMV=|KrI=@Gjq2wL zC}3TBto8A1iTz#MSVC<9a_v48t95M_q*ywzAjOR^ZuL?N^LS)v6*+*nULQrp@Ql?i zV{`pag8hWC+eMVNHv`6a9y2EU8=x(4)L7~a46tyg@R?)Swlk1;Hu5a_6uxyopJM0f S!^a}j&S8_m5xVI@Z0j%0pei8% 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 c5ff517d3ad7d41063ca80c5ab4a244520ecf78c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1650 zcmZ{k&2Jnv6u@oI=WaG>p-~k=KtmB!E5VAWaH$F^rKsXSs063cR@Us;*-kniTYF}c zT}@8upOHg*?3w?@S57(ifPf3{d6H%owI2D+Jp1{*H_t!2T3_!IXt$TU`+o+6{DsPD zb3wTeT|WgOh@d%%yEl!g)mhBo&2p!3W0!VwUhLU9zX;;MjyZW)^x_^RkBD%E_ksvd zx+hND2i+F|=z-PO1pS>1!!w9R_J-8B4-zfmePb$XE{|rJO6sgCd7Tw9MWfZmK)Db7 z%@znrFhNL;#*SdZc|l@VxZvC~-h*Z_zhl<4EXA)<>trfZAC_FS5I(T~1YLg%qGc^@ z9YI@9u+|mMX>jZdxAmv&IB3aB(vkz(1~qHF6Z*=5ANC-QzkHJIN0~T^?nU3-?MExE zn&D4G-$!?%==11S-Ka@*>#c>%wS15LuCV%AztoC7(A^*T0Ck-v$3;F_Xr{GRD1XCd=QHkZNz}n44G?ekdopN}oZ|&sQ3^ zObTfnU8`YeTrKl`h3Isl=F;>;reqg26iv5HrhmSgX95h8I^iNqYZDY*)n60!5%~D` z(Zjpr$BXo7qKe0=IsiB0pO2DamP`I)Q&-cxD#tUMPM3T#%f^R#3=7HchiO`;P^n{k^re{N!DGdI_A`0T(O&El-xB&tT$9oQzPQ zHXN7`(vf&pjeDT0Qfog!7QwI&FnfR z(MF0Y4mon82P&l%f+EyI)mEZX`!_U+qIR_s5>ijOEea=2eKTu2HVK!`j(<33Lu&={CYLksgQ$J%(T03MnpAK&I}I^2xKkgy1brBFUy zUtE|Cdt74UFNy0K6;f6@3~4ZCuI_l;j-T)aU&MkhSXOf(wei`~W16+tTj9KmcX%QCa%hyqYobt<0&+`zKSGj^@Im)Q=XV!C-!b;2Yi zH|xap=5UybH^01CBca5?A10j|u`f_e=zLZThgn4R1}V8t`3788^UF}O4bT-`k7l({ za*1`7reW`Rs|B!vO-kZjHC&IcH%v9Z)=dlwhG*#5EPJ6^jc?3lx73Qu%gbj@7Qb={ zb&H?dX62e!%oF#9=hTW9xhEHR&_201XOqU!s$ts28efmkW7N!I!!4Gt8MX}pES6Y$ zln?V*%^q!SG0MXfhZ*91M;{{n}6}LIA7D zXb=sKuO-(Hyt}GC)6)NFlbhP)TK@jrkBvXYC!hV^|409Zc797czuxLQU>eOol9>lA zcDf#>biL}}y2Zw6UB6j3tXM(^L6;r@c!cOU8y{xC5}E^}yn7U2rTqjw2UqfuG%U{v z5!xSPOU%gsqSzphmtI=bsnLi@OE86+gUSqAt<5w933(R*yM!AFCZxdFW!2Ug-+KTN%~CAiQ?P)TBCHIHRUv-wK5y*;c)!20BTD04ns$dQpv%KJfT8$ z0oVj}01AX?LRpY4r$K0#u2>G()E5k^PQui#c{%`2#JM=c98S_DK_#BA`+@uySM zKYg+F-kFW5{MJ-H>QRP9BE+B%4b222W6?nB4PZFHssu$K+1tTu@3%`&Mt|0S)i<=W zTiV(6R_Cmmry1~*zQtgduWz#{1NPgWuY>ZZz}NjTw#2^8rb7(^NqBvhuDgy+--P8b z^GeCfHQZdQR;hcfO=N=(=nJ?M_oNYeJlJ4(Cm75575c=?j9+J1Xl9) e$JP9EE_u-@|6oL5LEj(0J@K4NUc5%mFXcbMF<_(s 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 5c4e350d4f7043708494c689b9c511dc79d376be..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2081 zcmbVNO>7fK6rT0|+HsPW{6Q4RDyiDIfK3!yRfx7Kq@k|bfYer%ELE$uXK**Hy=Hc8 zB~)@rA)2Lv6!M8e0_KD(SCoPxMSE&N z4fnK)UeH6C>>8D1At{jrIYX#&g;13#cjU-^Ar(qBkm`NXftHjpHc(|bBMIe{?K7B1 z$}F(hrJBR-!11b9;8a*CvJ+9M%WRLs;u>KD1BD=^f=m-vNI_}I8AYh4>aE6t81Pw@ ze$IT~F0+!>XU2dvhPUm8R@#ZS)Rw8#R%xQGQ2DyPrctG>l@n`vo2`wW0I(Z_a| zZ9RySO4{n3?zuP-5^1R!ZS@=Hyy?(O=1KFN6DhNAcs0DT`M$YenzQCiQ0GN&W=Dg$ zKKl>-`B1;;RBhhcK&KlNNJbZ`?fWbcnqBegRh&!cL1>P@$SR&la(1!K{2)(7Dsuj1 ztHy+aNf$}4#;RvIWqdg!i*d}P)nrBAD_wvm>Yfxz)dpSJ4|v89iqG8hJPp1hR}HX2 z9nK=J2CYYrDpK9kmP0|o4s44$r9kMF=%svooj5PAmU3VDjQhEUQ}&v+dx7!XxiE&a zVY)s#m(SHgKDvQjb8<~TSNhhjRv~4%A{Iin))Izgm2B6wtkt2gII=N62;Tf9AeYJJ zI2oVn;BUG!KG8Wa+nGGr8J*f1HpWux$<0wRI(;Mg=f1bs_2)+Vu`zq!n7y%hD}OWp zQ}Z|Z-lx9~KQcbq21{i5wu%lhIhG|-mR0d+-Nkjx0?~t80F9!NC`Y+df25Pcl%L&D5^dy0080ca2<-)D%!4HEumm_GO~9x((|*3t}e4G z&u5PT#+0H*TVY8Ey#XZ;Pz4Oj4j@-`y(Z%#U3NXNX*F!O&P1wbo{vBfQ8kTWz)6nn zksn4ffg}u4i=6YLD47I;AP?ItM64hXeY|cAJu|9CEYH=0|5i3T5Xyjx-v9y-N|E%w zt6x7DnCk35@O{2BICAyUgTZNF*7MH~&30zyo|>~a&it6}%pAUzyP119v(S0#*weYg zKY#Z4$m#n>PCuMm+#1lvCN_u2*nDSVYHK9*T54SZXeI}DK`!{^i+huItw+YkAt08y z2?6joko2JRCbCHIzZ*)U>QWabZ({ZGb^qshs=D!uO=M;t`>4TWKLkBQoc}J6O<9tp uCuH~u8Gzq2GXI1OJtGUx$cdNQ0Vyx7FTNx=zkF{(T9SS^{x`un%+f!}U<=s* 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 46d6cd24218cbaa3652f424a9da020509ba22915..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1661 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 f*BI2B;(mbH ztPFJIOYlLwGVvAY!h1=tlmY`U^7DJL{oe2Wi=Lb75}>cI-VQ%TgnUEgDlt%2VA*A0 zl4L{%l9oY5Ga8^AmSGk)JIk14GCCz$v_@nsJEuf;6g>&EScb1jFFAU$uI0E?=}?<= zQ|IrMOZCVVx=wRZ%Tn2NUv1g;Am0(SD*NBTE9nKEJr`DOS6ZraFlvyt5r|PS*tY{j4X{CI1L}_s{Y+6yb-?R! zl_1g>7^95NQquwPw~#<57}g3bdmor0SpZ3=GJqsgNN^|_CRj!gTI{=P%B{AZjvlX; z!rK4It-oPaE!UN`Vq4`H^pp@It|qm85FDiQB$%*CIH40JgHO?MBYdUAY}CQ>RmOl2)x3)c;xQZa2N4rI9eAN=5F9J;e*Tl)l+f7wRL35*nwF zs&>V&P^N$V8I)C-JyEu=T6d`&8_~4dLswL429bYS=bQST2+xgOW9H3Q|_QYO{I z>FxATkJEioJB(>Z?5Rr_y(o(skUnZk?_)h20%jkNlqt0I3?caWQVaRAuuhx9&E{aE z7y8gTQ8TPfNyw;C^+|Bj)MRL4e~7KyJJyaoOC#s zI_KQXgJpw0t^mMBGl;_AJm3wBLPA);LYN9vdL45QHlJRbvHrg8PiL&Z1-}G?<6`EJ gg~)KE8Vi4O=#%9Z@Y4XU*;!zW%+e%)FI=4Y3DT@x(*OVf 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 2e0d25b5e24ff72ce7db7452ad3c1b0f45263454..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1646 zcmb7E&2Jk;6rcUf#*WpR1VcnAE2Rp!2t!ejsuWd%MEwv-8i~Tevbvgh#__iM;m)k% zCfZ0o^pFDw5J>HTLk_5b`p>wi0&7l@5C?8XflImYX4Y#+0|#bz-kZ-izu$Xr=G#)K zfS|njW25z@h0xEklNac#a&!&U14K|A5sm197V33flWij~>W0#0-6ZCHRJU|=6%p$` zA~w|@83;Xwnw7dujE_-uYWLl>m~;c`wqoXP#LYW2apO+n$C2B_5eX=Fdvud$6U{b` zXi)oAny6~R{19`x-W*$0$^C+!ra+9MrMLCp#vGS>b5 zS%mykufrL-uo<9;Lop?_A0=`q^dmovLF0R%x!mo#&pusQZhXQiV_iq!N&E7_TCDyA2m9PR7Mte{6>#ABtc*Zjf{J(vm>Vch|qgp)D4-8jsumS z1<^-`rE>quVX4x;@=LM&+2x0e2a8XN)u+Ykz^op@3UtghIqjn}F!KOWRM#MgI?*6# zIt0v+0V5{3z!HV4G+D_Y>|R(2Fz5d#KBCBJMAj(hc#Agk#F2n(s46^>!tl?qKqvs= z;~XVQ&D2Qs)Fj&1)}BT5)Y{VbtP~wgR!D<+Nzv^|5L4@TP$4;Gs+qfguF<3&6E}%n z3D6BQgtGsixmGB+-KPzNKS3M zO(!rQsFngLER|2fj$19jEbIz+IyEqun zOnDxOo1VvBhE41>5Z@zZKrvt?IVwdNDt`w=fAS5@O9S)L$VM~s{ne2^M1zaGFyZ(d5<^) zF)}nw8={K?bny^f7##nHsMuHE5S9As8`-vY9=x237v?o!a#DOg_nRF3{a>aOroRBh C!M!8^ 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 41eebcff40c6b37f4ef245d998dc2a5a1553f734..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1487 zcmb7Ey>A>v6rcU{`p&21uybG_u)>karnplOK`0;#NR-5djU_^J1*`GSti8#8TxRBc zIqNK4;lflPrAP`2Qvu3fK}{2c5YD8rlu)E|xX^@#H@mmCFrwf|``*l(nfHG0Yk#WM zDhTMwkKOQ&h0tTQDGByDjXnZ#7ZKD(L?e2vC3;)e)Y^!RwvkJ-Z4z@EwJjZ8Ma0@h z#HRXvqcG66`-V5M`OZ>Cx-oUbjJYdWaFYr*>xd{z-2kT~rrhn(RbCtf>o}!x^Qki6 zY0~@zbGjT9<_d0tQ1(af0Rwc}79#qVCbZ%hp&NR+I+6o4(C&}*pCQj!j&`OIicY)@ zUUX?Qz>y_#LdhrucxxkC%X&DzNm;9i=+iu)k5?93 zoji~tK-`I1J>F75K~%6P;%#>NaxzK@z3r#ngsDn6@bS~YhG@T5A70t7HHKH7RO??Y ze7SIMes}(ts`tS3MsNZBBO7JZqgUbNE~2QdLA`XML49C_6;KvEj?>5#|Vv>HrxZqZ^n5W1tUAqJ3j+SwtULYxX<0q*KN$tsJv- zl!4rhNwC0NbwQC_HVI4V_j~I3Ax&_&&)3-)FDDBlae+A5d-}2h3_pt5_XAuGltuXnt^D zqp8{9<&imUT_|p=JKhAF10mgG!`|%iW;yK5Rrqx=)!}SLF*P&!s28rzsI9`EyMN() wSioLj^Pp36!`}jSU}&255WW99I{ye&AEDYGwym9e{?e>=;p>?{5s2Kz-z-m%t^fc4 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 6e03805745c9322843fd5329ba0b68e7f69303a5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1263 zcmb7EJ8u**5VpOK+Z+UhKu|!j9b9t=$_Sx|AcZDTBp$`;B+FU*62tELV*7F=x~oV* zPfI~c#6NON#a{plW_EJ{@hI@hZct7y#%Mp2KdsEFD%E@BzW`hdpGI~k|+421?Bd3+h$HwM6 zrhKJ*ZYExvCND)JE9LT;+I8JQxi1=34Zi>?8PE_v5>9QGxWmCj!DNNe5b+Q2Ga!}^g}@Sl8LFV{xT zow9E9LhqT;W943XGa2m~Kbo}kI@)5QM>97T`_aAzu@2MS0-;DjA<9frh$@4KMl!~T$^`sLLw{5GjSl7b-bN*y`=4Nj3r;n1 zOF1X@REeJAKyEnOSPnt)5WhEc9?zpWo5zyAPZo(}^JI@Ll6f27m#{m<{9|6@<8NEa z?;DwWlOsT0cL=!uXJY&P{A{dEgcWrzO0N$U*K#d?wy(~%cfmub5-nBZ^;j!A{QWb? zqq2vh>b~mWayhl4t*bj;*J=#ryQ1@8_>9ru4@;$)Hoh|hY1>scH{<2){K!o6nP@!v zv@Z_TIgLS5BrVC1v?&j%8IOT;NGHl_BSVZ7!j;Mh`9wQ!?9onpusw)EM z*xNKho8HSxZVznlnJ_z64SNmpS2ge_8!sv`zF#s~^@cU@#vwM8y|L+eC%_9}8vnY( z12-cpF7|0PDiaSp27*WkpKby7i-+|VkY$+WHV}a_3^^biL(B=pJK&*@H;=&6*tJ)M zU1=;GK3pl7G5-^4cg2W`uL)z=w#a=9ro&_OvyOBZ7z@!o#*=UoOvohU_+xYwaWaXv z$x$?^qyG-1Q(qxI^Wovyny>@KQ>#*MBrO{&UjAq5+wJsxEDo7wWy*3Z_XL%EF?}-- z=i&qK66#2DQCZoSLU(_^23Zz*j}^_W#$3urn$?we*UD1#L48w}78IW&>inT8SEI@{ zY9M`ELZ_-fJ)It?Q95Ro^*#;Qp16R~iL$5$>7qLIE>`_MV0Q6PXsNghJp?yjD8`>l zW0gMGs5jO-feVb)?uL&Uz32_9#qLNL>%t0PL*7Fpihee85g(UKeRffe*(F{;`8bfN^@2el->l%TmypIF(%D-7AFZgT=-dA-RbFdunku|3QAp@(z5Pv;Abh_ z9kJqkr$s4OIi-3Y6l-te5&&Ol+N03EAN+~BClVY6&=dY&r=@Sf;?mzbtUET{X@_;U o;C;~kNziki1fKYYsx|!0oJ*D(xQ{U$GyW9}$t+HA5+pM}0BpBn{{R30 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 3e01754fdd587427241831d447be33326034f96d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1286 zcmb7Ey>HYo6nC7DOOIAiihzM38@(k6#;QVvQWYe6IssF3lI6xuo07y=+t=%z?xuEN zXJw!x|B_cG{sp@5oZOXC#DFb7zZX02{oa@9%1Rr-Sf4!2KKlrLx5D+|fv^QjZ31B^ zLCj&ea7vtD$MSCBCT_iZiN`$VpQFUzMJ!;gbHrL4pSelE-1n#xPM+-vHY|9Q2^sZ7 z`jP8M40JB4D5VuExQa%6Uo{8mA+2~3{{X9~;~4KHRlJ*8X)!P`7Tq>1bq@%0W{BaL zqtRDqil)v52CHs2|Fnise)}=#!6uMkNtC8#_q@^_oCIZF<)wg~p2GeONr(gbx$|nH z-x~uAS@xtj;;HTfd|r^ZZ@N1#-t`AH&IUgnhYh0oj739s%JfaUkTFIS^tD;dPm3!^?bdEHLd|uD2UYlc(DSRqB5t zYi=oCu|2L7J>aQzc`+G>&#h>{MPyGWt^QU#Nk3hPx1z{8I zO)mGiJ`^m{BCq1-CdC45!iAD;OQ$_%%r5*Qqz2HE9BoWq{lS11@vH%xWPhdz~vSULb?DlN@@ z8#8j%3PkebAy?XX6~KnPdx{rXvUC+66E1usl z07=~eg4%(Dn|T?8A)c)LuB~`+y9>7ASBruW(>+Zp&}LXL;Dn4h!=VIn0&-UY?- zp(VDwt`Unu+J0DtRrf^bcrNsW{nlwoTl^-v(P8mO#fuJ$_u)B^_63m3mbjMKhN?AO bvs4>yoZf8UK6l`nvX7pJmz~fF-Eip_C8b-- 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 34a0144807080ba6db9d7e6158eb6d346fc48e62..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1034 zcma)5J#P~+7`A;*dZZ~s1xySc$#N`6l`4dQA~7XYD$^;F<(}W9SA6GSJFObRM1KYY zjQk1y1e;eTBz^${!oEw(sVrEs&+qH=^E~f|_0G-!h4Jm(WcHIF^gSBgL4)x$O#3J- z66Hu@NffR~PH@~OD#?@POLO`XNh*7nNcMnyNb;Ucj?k#T+8b-RRKRB1utQzE0rzU* zN?o(!MAUO&_M$r0ik-u;ZB83MsO(3eL?i6!b75gpM5{Z3+*aECumP0gAdg5K4=GGit`{$%fce@8n1~Y zAIY4&PuA!YTBB3EPCm!9#kA1X*D<&gEt%6S!m_Hxjdmewslfi5lEHv&#{NPWQ87`t z@(g$>_u1ZL?*I3!idc2&Fi`9g7y90ofv$;Pm${UES?AOm_9>B(zev7Ugs92Rzb-&JOz_ z=WmxnZBDj0m%8BGqyg1L{3e3VvJ-u=gW?}HE-9jzCPUJAM}4y!e4gEiC_A^=RYciw rcortU0#9SXeBXq&(NzP`p^CXPkUAU5 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 8ed93bbe79cfc3990d1d38093fdaed8834ea3916..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1288 zcma)5%WD%s7@vJ4Zu2Nq=|hk*QV@d}6@)@x&<9AB)VVhJR8>O7LRTtJ+<>cq@A1+=u4Q> z^sS@?IT2^0ZO>ii*gVY4idNF{eb|i{$EOtyH z$uei9#NOq(h+Se+KA5yr3U+CxiWznwF8bCkMHZQXPb#!~yd!cz zeP|Kt`c}!JEPwHt<@%KEVmp2nyMv_0a8$XQzU9!8FdtXd7lylta%Yx;OkChN^Eow0 zL|%hrl`_jC>q0Xo<3jz)BSv>>l>3413e!0~&^fhB!B9Vj!06!$(ggb$ai3A_a9INx z_Nlo%V?O1Sar2RDl`FovLix7uRn7B_x45zD()w)&TdrA+S}yJZT(!(PHzjJ9+Obfk zDDKaEbuTawDq`D4s4;rJsq#Jbuz>ZSe>7%48?)`+cOv^>J(raJWFim>QV2QG6bU(l&`u58-J44Y5wD04 z%ZNwpvS9B4yCUTg$>cak%n$;gmK>2nJbfOxnpM*|_)x z@Iun^b)N4fEx#p(mC1PsblD_9l8ILJoEXT2(4T=jHRj@=7MPr4IS^cqd8jFh(gE{r mF#iqQYybUqK)MyJ4#>8m6(&a%fqam?y8laRfB)ku0`Uj*MQZK< 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 27538988dc83db3c117a90a98da302953849ce91..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1148 zcma)5y>AmS6!(23T$2>3pnia$GOC1nOHN2gl}bS>t%QoyPzhMFSnk(1^^cX(-(at*VvncGWh)JwWUoKyuXFvvh1Vw`IBc z_RMlZnjwX?gu#`>U#IDVMj9kB^w)4)r;u+&t4Ro3bd_hPnV*K`A1aG$2>UMPwBqZW zS{i+$cUdaCfP$(a37W)^gv_c5Ms_xF@ec{Q4vK>W3o&+ z$93xWpOSlfeIW(8eQ2wj!8)Qza=XaV1LavFvAhVej0326p&f?XpQzwPu75@Fd#HegIYjY z`D|Dar<85tP)BJvNSX|1eVa~$h}L9!-P2Si?lW>vGKrFIudcjnid=>&@(s#@gjA$w zOuY*o_Z>01(WE>Tb}Uo7wy-%3Yhvj54Z<1l5=DYjjCjf@j=1W9diGJpd(0{0-eyow zS~y&%%sV}B^D9eUBO5W@b=(NN7WWj-n1))ZQ>5py+}IKhLOu>dgxaI0$EZATpO?Vg zlVTfm&x48U`=gWlW0Svf<}~aAbNt%#V%Gsf!(W`KPtMe<#Wzc@m)<`8?A+?9=2zUj z0#I*7NE8r?vTq{w5<(kI9QIGnAw&`%Ay$$>?1B{U0K2I45hc_=&4eQru3S6dz@G2s zLFv-=V$Uw-=JaiK(ap1Qxe0it4CNLt56VzpmEVo2nHIKcw8%3#Zb_t(CZzua{m7Zq qtnaWZ@<0tczbZx7G7RGY-2M)39D?E@82+6fG3H*}`~&2ch5rT5SwjE- 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 fd305e0e2778777340b6df1cf45309d99117662f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1009 zcma)5KX2496t^AU^|)RckYHl)$Sq+(s#GC_S_D%IQh_0gWclKE*H9-;ZC|UNj){H@ z1{e|Ffe+%935idD7_gJOaw-d!?7a69|9-#sC;K})LloBUule*RLFjvQ){6$?Y1sBr zI3&uD#F8jnk&NJICo0L3_DM7P0!b=+S4j4Nd`PmMOpejGzt}z0a;|_)wPCMxaSHBb z<4Rq#;zZOlVD_?_XvHpIV%xWE98~%vP@*yRbYECFDx$YuL1riGelP%KI7lNB2Pq_@ zGLiHO1^Qm3_oIWB=D1~9>>em#?S>Yn|4WSDwouDMuvW~VNLE5(dE!0@+p=&-yu?c) z$wx9H?~^6^gqG+XUM8Poxneo!;@cs(6D^t3EJCuX!;F3*YN^2fTZ-Y3t=3*6jHs9> zTzL+>lzZ&;39Q_QAv18Slu)~JT7pS$UMp({=3J=mR!7I=+=#Xx4_sNnG{ASgashay zX#5u`8+C2#>NzI9*RuM41E$pSC`9hXy!drLxq-7eSm$XSpvTlx3u@|bUFYBjYhBs4 z6i#sK42bFrGNVw^U(buZ{EY=;^S9+p=g+{NI^E2h*sVc0&=$c256R;0=8UB4?~cOsgiBF1=YGg})eds7N1UI{g<8GY z;#}&2a}zO5L~n&}96K=$#`~jLOAPN8g 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 027db20c710e88c26904f938c484beb3dc2d0e7a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1045 zcma)5&2AGh5VpNex}+%wBsg*KkqEI|kSbLO0YO3tHB>6StX8tz^(0xkf3=-PjlzjO z1_wCu5-v=jO}l}`SH=t&H%Y_|64NsNf7$(mF}QUdFrNq zJHmwvpsL~TK zZ)%fOB}?a`oB`A4#c`FhB^>K!x3NPW{ctYP2wVDGXqcp4>kdb^rFPc|fMV?E5s4ig z5>pvSdVw5;o~K}k!~PZ4*K7e3HRSC-ng~-aJZrZ7> zt*EnPsT0q+guE4Iz}w!7>T?@%o{Bu@{HCvA^D4FocHi%iu5NexC%Qa2ah>I^`Di$| z1I~-4(VaZx{B$Am&CV9*vPwBup@UW)zX_{>k0 wQb(8ZM91!pDE}lZ_0_X|<$9V`29t@SoIPC*m z6iJDS6xC5dQyK+1)^Qrwf0nWrL^0L5AgTlOW1MzWd_acX<<4HE78>}pGJL`0a~;P$*YtTaD^LtF3GcF@TWge1erv!~L+I15^PAl-)AT@-+%5z?b7LOL{M zDpu@*Acan#;0L1`>Yzqi?u@jww#A26|3%1eS}4^XSSx3c#Vs-+O<|63T8@i~R?&(o z`iaiyhj>LklNC9RR`HjRwvZOK{I&<~s8ZZjJhKHON!Oj9LUcVeafAV%Rue+xyrJU!JGdl?s zOVEul1JSfvGC%h{Au_485H~}~>PN6aBm92%*z$I}U6RX_V{9b67RU?X`$80TU!yz_ z;$$K9dS^ojRb@h$9=bNcf6c$OYz15BpK!ufjN5Vaj=iMHtl0pdmu9J&W0_PRo-EucNIs>CgdWO-wENpU`Eds!MSr}k%X zphy0aublV`Twt8-R#8WqQO22h^Pb1e;o%s89d*<5Ta3_epA3ycatv;cAP6L|LKV(1 z_TNO2jLeY;1ie9mN_-t;fgqpIB>V%_Xo4L*s*mifly&`{S> znfw747co|Fr5KC+<@pgLBzW{M3Lca0>3nei>ArFSn}IejnlU$KtZKv><}(*FcDd$i z_oo-3V?gYg55j>z3m}BxW&|NZ6vw1Lo6szIARyhCbT1%X!i&;=pqL-e0v&qKV`ex^w_h#n3_s!e)VRSSLDA&K(tM3TF zcV(oK87RG5sB8lVPF$#wG9gOWT&=8WumD`&0&WPhs{`!e>`Pha+G8kWekij*B4dt8 zu~ZqHCJW|Eq&I=fHV7ya4rPrK<+}xCgXVD|dJxLudXD!!}p;Zqo#|ww|TKTF7r&b29Y3b9&QbCIhkJUtU>V*g- zy0@<29h%-jck8HK^j|I&^eE#87&p40%0ZiAMb<0p=oN^KhLB-kB6k&73U@U!I18Cn z^eyY~*}Yl&sl-h71J9|hh4#FV>tWrumy-yOk{8@vUi3tBs>U48_LKeo%b4%jO=(xw znCA&s+Q*1RCZ!eTx|FugFC6)6#dKTIlJ^nX5NF}i)$V00*0u4oF(AA)eviQEvCi0y zeY136mO5tXo03K>w z8qDpVBoztHpu;B!#&HS86sRUoKq>MXLVGy6xrz3CY-BSxV>k)@Aj%DU)H+tAVpA5= zA{AG#^#j$Kkr6sI(a(7f?Y}$@&D(w2cY=#}pgK}ML5OuiNEfC%|9)M_wUZH#j1pvb Q65h=GRJ*_abChKL1K05ZHvj+t 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 53705f2d5fda68fbbe1324cb77731d9f75517125..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 876 zcmZ8fF>ljQ5WZ(S!K7)Z5EKvtmO3C0*s0Vm6+)t_6lG}X!eSvkz52ZdQ^!vIoJNU? z)S&|-TNeg80qU<{W&sIAWrTzT3tLgj(usR^QmUNfdw1`?yZi3>K1@$n0Oi;F?&cu@ z_^F(+lt#wTLE`{8a1uhBc!Vfh3pG#EpaERp2W|**q!)Nk?`wAHw{mQf8OuzHHNOZI z8vEojoWf3D>y(N_$Sya}+m@MmB+6cs8Pm@N>aiRcAtW$&prE5$Kof@?gGVjby3A64tij zP8!6Kqyd*pRv5_CiW4h{(sSIP+7ju#jnNbXecq|tT4qG7EwW2HB9WLrxPurv#$dX2 z(RVuiHCxY0odoMd!Y+mo*UltqyUr8_|-Gb#017*aa8-nj68obk0H zBI~I3;G_b#?jGN^{^;7w)fdKT87|j8*4)o#{fAloYR-K!=U%V8X&yG;reDm3A?CoE zT~Zy?^H7>qC~e2Q8!Ed>>2{ZeMI;|Lxq=DrAeq4`HaJj6BnVp_=#xpw`xUq@alFUehwkYaeL;ruTGxL5y?Ll$_18TjbGEQ_t$ak3k S22-cyX|nio;V(Ov{PXQ=Q5jb$k}EER@Rr3r3W zDvcxrKbc#nX1dgMZXrDaTbpwWpjS3Wl7kQw5qymbPth1oAm7AS?vT(m(%iD|e9JzG zEx#2=dW6sq`qOcAC(~AUx{8|C6-^DQFffhE0$kK|QBk}9dHrN%-Sg!gn?5*AUc|!7 zT36OT{A@eG^8N&K)IJBxqr*69zbxXwkJ=7#{bL$OZR`@7{1U4=d$qRUI+})n!Il?Z z8twEC*3s+yd7DqO_kf+U55=m<-dMPFO`9znm$d`NZU)^J*#@%wqpoXPLUxaW50%Pw zSt=FM&d9Kl;Gqab|7d=&@MNF2z^A|>np7!2R;p^ux+H(1)YV#-yPJV|H$k|8?twX? zvw-@b0u2~)DTb3+rqMm}$p(@=`DBTor=v&q(iN2*Y7UTTFCD@A@a2xxEBYBjj|mTh Ef946g$p8QV 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 3e1fd5a0abf6d1e8edd3b44119363598d0caec7a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 765 zcmZ8fO>fjN5VakrMBD8V#El#{5TQo&xcPVLX& zK#%+(4!&~YFK~gGY`2O!l1CnU=J`F(obK(72-x#q>Ddh>+Ro%b*8bE_4QD?+oFP=rJtw6r7?6^qqa97KmK|-8K0^ z*W@!>(;EileSqKT4o2avO&ZzCGOQb$*Od{uGLx{#jScHI%qx4B*Nqq2?$2)-b>2bz zB4UnLvefRu7u^~t@18)8*yccaIE>@w>n!q|-?V`19-$<$(sNU17f{ve%cVB9MKuU) zbaCdml2-mCEq3bfr{hU_YT%{xU6n7+ZTd!=3tKnoNssWMSMd7eZKYTHrOd0e>CZRH zWRs^WlV<0#s&rw}d&FEIL?(+uhyZ`GpA4T-_HV}vSod%`XhyuvoI#tQ41YifHxi<( z)w00-ScuD|EH*d2qIL|hy^Mj|#WN3_k8h&zfO6KKL_7)a(T`Uq-qMffFkCG*-*KCl Zx~~}^BmWlRUGQ?l>J?5+|HhaG-hW6|!;Sy| 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 47bd32f930da5009e98478acf21cbe9b3f23f527..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 748 zcmZ8fO>Yx15VgHdyGqj|h+Fr-p*gT3*9f7~f+7%&D!8pyvYfSBws=2kZ-}}Ir}k%X zphy0aublV`TwrF?RI83OBac1r`Hekte}7284%^B39VO&9CVk_O9D%z-2#P3bNI?@y zam@@%*o>$^@mr#}rZ+(nDE66*2Y;ZNjH&0x(&>4IS>G5WN8s)m1WgjENWv6Fv+AVA@Ppa{uYZayiIrZsD*Fm3tFM;YH5R);VyDfy z=Snv67qR$Le?PpPrf1jL7io(#TV3j`Nlz=KjrjO}{_f4EwC;#DjmSDrS1yI(ved?< zk97D@h)fzIM2HX94Z?s?_HQQ$S^H$xd;54>Ifq{X7&a_~9|}=aYH4si65?tp&E}+Q zz>a{nQ{4-DxEDYe;GNiKNIC0v#yp80$&Xhq-pY>`FiRHy+Vdtabcg9NqkfC@K76%N N^%{qyyJJMd;6DhIyVw8# 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 0b94b8ce3a8be92612072cccdcb0dfb48dd93c6b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 792 zcmb7C!H&}~5Vak*l&a;hLVM!i1D6UZ5^$>!+GSY~hb@(sQxwVa#%`O%iL-X9veDk) zL*U3Kkt-*DffF<7c2yxRj5HIE$C-KaX54r@Bp^?kr;8tyke}%63kT;2G&}~QNkTN$ z%+i7-jFPuRN1A^jnj7{xN+R7mCzF2j{LJaKHDcksn7izwsm1%M&Rr>v=kG)axfUC9 z5w?+RrAlMtUyz$Fm7*53z#0qfdHswKkz{d8rBmaUP6a zc*Ojj_yK%=zj-}P=hr~9FXrA|nygMwKdGX!Mjo$gx3sQIt8Nyq8bQ4mX3k)Wt zNJ?B?w*x6NWv!GWBmljA6n^JiLYgK66=GsI(J4Y$P|DCKLV`v@6>2C7ERm&?o4X{Yj-C1(MMN1g zFz^quc0dKxKNApw;t7d~EhtQ#cxNY(AYFL&z4y-V{J!trd+!Q`9H89#Vl}@JfFF9Z zko4S{ZJ{#;0d){W7%u4;b%THbk3pnfgP_tlO#z(YN~E3==^7L>gM0hF=(&MHvke_+biK|v(&)X6cAXTiT zBcvBPO-Bai@^H0iMAtO}Uly1L^0+5e7*WseNQ_e@-DZ%%nMKjP5&NhtT}QL~D#~)+uw9q) zK|wdgTx|0i_$I0mOmnbS_?q4Pl-)e3yxn^re#~xv&TdZ@?+i~twV2VlGQ zxX{mYe%!O&dE^S`!f$XMtl>|AM)?3iLHp}k>8XxYeDOQD8!ex6TAtAIGz0mKQDsWg z>HAYyRY@|Pr&^@JRkRWNrx0denTtZ0hd9WA9-st!Lwid;7uj-hj|agu4Co0`&rnTM igphOCnZV9>xI6j#oo3bvQd?DTwtwlbzb^^JkbeL~Nd{p6 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 4d88ce06e2f271b04fd98e7b707a14c45db09720..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 885 zcmb7Cy>1gh5S~4+jpNt_5duWDQmUi46Oa@{77`GIT*O2Inrl{<+qHA#>~nU{ShiB6 zNWmMpg$huff|`l~Azup=be0TFDrWD_7D6~kqA&#k9ta|=i1aMaB4EK|5T!E^R9fey+}A6O%XVdQXD=4RNU~0>*nS)w$%H)_ zBw^fV_xD>&>rk+fJkYr%IQ086a(rLxj^0~UTKpV8}iiu!Aa-P;W?`125a-H+zp&u2NOPq^1;9RZYCuL&4 z2h}k0x?yH#GnN&28MQN*(dN7Y^^Mcr1ud7ib8q9=wy82MDf_EFXQZ8Fea->yqcWS2 t2j4K#D&a-7nFGD2uAsrJh`xqwULu6d;O=+0^|P{0wqI@i0=%-1zX6{xgCyM1yESE zaWmLu@};R`ma}5FHLMIF@o`AuLBUk;)x@+S?(w@*InApB$G4 z;J@HoNwjjpBBL*LevA$eF2 zaHwu#C-h7cL~Xm#4949}$ASl}!hCxpvUBvN9`&c?qtZ z2jcvdg#mtkJY5oyo~)7y?o-(;Ve#(la*476^CTCcjf<9ej^<=T`tnR{AWpKBfZSSrw4>loe% z_vtLcKsSKGfXhgV?ra*%@Boim^%w7C1IeCuGQ)Qhh-UZCo!TTuAb@u8Y)9|~K5_=6 Il@z1s4<1&^VE_OC 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 30c5b8bca03b717ed851f2b706e258b57d377ece..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 801 zcmb7DJ&)5s5ZzsGav@46D5$R3f+j+^fG$GlASWS&E}Rsuu`I3jyqm8<=Xt8 zhaC>ptxYTAP2O@cRqxjh8z2Z|Dz&bt(`D*>c(-{z5zRxIpzr9@-d%4T)Qv1m?yxSw zWlK+(zcD|+$M3~#8ef3(5Wg+b)w+&P9bDCA6?f8f%AhcX+dj!nTEtZ=OxY@Csx;n0 zOd_F55EDaT^|2XfotP}sdPK~$l=XIFVm{ng=94~cBV1ZHL#^}nR^a+6llZYxJx z+cMGG_pz0y+t}hh2={0$z(6~I!hp*_igs;2k^Vj|HR>Tfu<{GD62#{q4(#BJg~Rs6X=T?rsAc8UqDntOqu$p|R+Z{-aiYA>tlGf2 z-$v4vDIt%3VR;tvP97N#DLMNh&^bW$kI?{&IU*63V@DCoErxio?cT{VY|uVG&W-o~ z1)-A!Eu~&Np))aXjOrhvxnW;XFZm7shVQo=-z(aSQ)pI|RZXQllvWksy}5wi|L0@J zV|6RD+Bj46oXph66<`4bflOr<$|hT8;G(shk6Mw(r*aMdNGO@i}*T)#V+zd8Jn)cqW)en6(ql8sMjCO8zI5Ft^0 z-Ho(PO`dB#AtHK)2JMuHj&_lGIHWX!p>;Rby6Bex&l9b$)+WD=+|$}tsn%|Yr5r6^ z4|C7lr@H_RWq2|pE(0n0yLl+x1H9_gU$~=zOCQq?iPM09gpiaR2}S 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 277920a5b9e8d5a0023edf69054a887500b8bf36..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 793 zcma)4v2NQi5G5s9R+P9?hm0J$1O-%dHHso_5_GC-pq&H(S6QSJr?RM$GExiK;!nxq zU((vY$do(E4br4T58&a*liqvxNIe?G4C8hCc7Dql`%Rn25#Z(+mp#NJS;{^!$)&ep z(o_7#Pd({>Wk*5#=ChWoQi-`XVycTDsu5r6W}z$bF}F%wshRCh_3Tm=O;RA-U*Y5K z7?*v8NwJh8nJ0PbOQhJaG*AN>$iar)@HCVmk`B9ou-oLM%&q-@W*qY&sP=H3;Iia| zWY-KZBR-#OdAbk80Z`>jwdGx4RXN`>Gr&*p&Q8bKbX{EJW;r#Q#>&oq<;%KM@L|>H z^HNt??WWewio9NAS2pV%u-OcOlX~4n0E=qT07N8$)Wp0U5pD9PQ#{p7d#5?X;+kDL zz3q3DQ7hA|MWe+zUcpn_1&xu!vDn4fsAgp1g(S+B1AkSWLa9=c+Ty+GN&U3Cdf>W%XFSY_$c~?j3 zT&xqrz})FLQvN?b%3c}qlcf0u0!jV~Y*lmtS(L_^5|Ew9!y+=uD#8$EcQ(iRNqLOVoPN0Vrz-MORqN7MB?7rgsee3S*ypPG?amFy;m=#sppJ zxEL;E37XLvq^(kFLDNV={uM%lC zBcR@mnkH`I8t1dK%m{B`=ISRdi;puIPsD93&>_jE2#KaB$_%!qu$APi3KCw2@uLgM K{`yBLCHM`yqwlf+ 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 77965a69becbeb8401f1f2da57d80d52bbc80de0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 879 zcma)4J8u&~5T3o49XpPA1PWV1TOql*t3W71NIWD&fw2TM*DPn>IyiBB$L<-+4n+zV zp+J=W12j>71vL#NxfXta!O)~)=I$I&XqaO5o1NX6`DW%*tyTe)Zy!g!4-DWd87r|1 zu)Ky~4k7qpz(WYx3_?S)V>9>r#>8w|Q}1yUjs}wVBE{QLcULC-Nu2bfA-@-B$;Yyz z^V_JiBfE*)U8SMZD2(NG1apw!GfZcM%r`@02EHZj(44_>@yvH7R?|)`iad4i4+5?K zKQGce6Us|8mX}Zw#Wx_%oD*oW))7mqg7OG44E7|NmX?E_rYQXMf%jZXrM=hv?Pwef zcBS%i?m6|l?MGgmv5vA<1aaRRYcKDB_Bt5WjVEbYi2ksj2r;cuCU<3)F^675bpT(k z)(@?B)g=|z{hI;dj6og29P8hqC+FlTGMT<;t043{I$g_%5v-Db1Jwa6*5ShD`~A23pUZVc z_pN4$nnBNmNGn3@WjlpLZE4n zXK-TO`r$CUv2eiI_-@#CV`;8X{*`{Unx?vlXX@nB%0peon?K=C%eV6a@2D&2pi$^e XR11?ab^Ee_Pl 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 dbb7ac8cee3f96066e10a9960edd87f180d00000..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 766 zcma)4J#X7E5G5sAR-Jn9$f1iDbvJ?{ND6eS8=xP9K;SBibgINLRgy*!AzS=Yx)l9K zTKg}G4!xrsBW;%+aOB-leD~f_r_@*U66Q z#PIvHOmzB=%|!R&jn&(=kt^%uxvj5EkZ;@2*o{1`ypcC%>4&OaUYa@-HNqV%#pndx zKgY$el4GMp^D@;4zhk8^BQ5l3$L@KV=?oi=dqKEeakj3!|NpKiFB^_tx= zz!kCiV#fO;AbfyLb!{*e-mF*On4@w0Jg(>JgE!8rkIjWuuZ+J6wpB|TR2xOwsJv<$ zb>r0_*sCSt742=G12mf^0OZu0NHE?_sax@IQa`3iaip1HaK|nq#qQI&v#oLARt76C zaN2)QSQ7JMGLd_mI}^5Uq2%Lvn65c&~DgMh4i z5(Q9ofscb4DFo;z0N3%)!1yV^muhpH5$BqANM-z9{)6pO(^K>ES hBF)oSTg*~NRQhZfbu2z2d_d^)ZDDBX#fIJDm@Q0|NH?KAZobggjvK)EFd3;PxdPlBDDlkyHj7 zA_GP5!ZeWKS8~9bH(#_|Efk+?!>78qR*s+4uGAI(kXyyqYG%8to?WTJB?Ywm!$Qc8 z!0jtI6iF$71(K$r1c(hund(U5Zj$jLxAy;$f##!O9Dt9(Eh-_& zEfH`+e>&OHbWb3L5LLcZTiPF8iqLwbOOh3DEI> zKRvG0`f!<-RaScmwAUrC%WQ44P7a&R041q!+E|FPDxDB9PK2Wo+6-}q_%D5ZuG{1m-Jl_FFPRU@vcQ!K0txRP{{OnTsORndCeU&`EH#nWa zy?Hsh14n2XcHV=iBM>z&JOQjuU~Rw=Lm}FM5KI5xK)f%+_f@{w-SmZ!x)8$jv0+^A zxEPe>2^{XhAf`bP@MpF7BBm^i!+`qg<{9 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 cd49bfbd4df103febb863d114283fb8aeb67d3b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 785 zcma)4J#X7E5G5sAR-Jh7$e~LRKt)%hDDu&wDA2k_+DQ;_l|{O7BAY5HBe0My{wZDj zm$dd@WXc`o1WDSV2i)QANZq~n#C$j$Fto$w&FnX4><<->O<-|^?p|V0EM*^=;wsoM z6=;4Frhy8-u>;Y({$kXs)N*F5oS5QL*Yays&&^VvgJ#}5zOfPg%Ck2jsVj)&X z=#r%;ff7Qh3+Y#dK-4b(?~_QwBHJv-6W@F?%e-xMw$;sd4PV3?nnv6 zt{Gs&`Fy*74Oi-|QvD?=4@37r-5;`<6=pcN9#y-OFJ!z oKyTC0jhKrt4g>DHJjGurrVhvpp(^Vmgr2UZcfRsaA1 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 ed21b2765833bd1102259880bb8484bb9837cb5b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3697 zcmc&%&2QsG6!$oe9o`}U|yys%uS}lyi6-QTGN_* z{4k_rpV|G8+uNb{h(-1fqi7HY_Ldt^p9%Yj?FqFI?j15OYF)tBv?hs-FI~Yroa9>x zCikI>T@Xy`5-?1sq+6moEuCpygO#a4%V*jt>6$P$X+@4JFt+HD99uB1(q%bbf^m)3 z<+uvt23?WkWx5IgZp5atbF#VRyFx6$;Yl1VPX~L_hGXno&@a)+C*(wPz<+S0KKZ!kSa|=Hud(=Kow^ zzUPO6UC;g*)eHc-RNlct0JX%$a_?q_;HB9MkgHchD7t}z!T!PZ5acy80GMKm0i>~o zTi?smO7$He1_*1TFg~K=(KX*!UNZ$4m-jhiL0saDcA7e0MHp)+o)<27(x;## zFd5fOgVcyYjHmN~gKC{-4KrNe4w>V`RmT}h27__UaUPFdKUt|b4h=oW;do?hjglP3 z>uAf=s&$-hpty-*nvb%W=J?()S&275SeijBT{o*lugv!bk-8qJ%``j!oA?z(o)e#F zeV}aMMQe^1&BF>XaaT&6X}j88N}>n_8+*5itLE@E=0h!PWpXQ};} zcZHT?m(U|E0-8M4|JMJ}2+#viW{JYnVV|8w$%6jtKRMEZ(kHoR24{nOuvogK!3oY98%U7 z)&qk;L~h_QESuW$X4|_8c;;zLA$JFlv+|~OBw9&%(@K2PQd{orPOB|hQ=IEE@+7Xs z-j_xu#n>;1487l|Oue61DD($MMgi~tIqLzagTsKnI2T~4#NS7;D9P59c?ozI)jj|* zd09DAv}V??#QxVTc5lo%3trs)Up$3Y48D=rm!Ln)&~K#ZXBCsO0gea2{eg(;-rt9S@F_26Q z=oa=hA`@q)Y09t1n{$PKJ~H;;Ul_;79GfawR3ym6E5AJzvO=})D#E;nX;I*!q1E)& I()wrr0|$g@cmMzZ 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 1a6608a2d74f0bed525fcd4c60dc6702e938bde1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4968 zcmc&&%}*Og6rWx1n#I_dB$Vcx4GBdq#3@nJq$El|s9Hr)Xe638vMg;J?*dMY*O^&L zV{t$#QaL0Qmx`+DAtzeIUsCmABCAC!RjSleZjMN$Dwp=Xnf2nuHbG6=tj%Ng&BxB~ z_h#Pw)<48z5dzoXFX@RdI|%s$Z)%t76*kA9uuM#nBBo@@d1+cs$r6#tN5l**5L2;2 zD7}1v76k zeb&0kM1_6xmX&pqk8t>eEf$nh1}$nlm5VB zw;j8f7C{v;eedd}f;D?`I+H7;i#(<_k9DS)OV6^jh%ZZv;aReH*VPOoSI9YrQHqNh zC2Nj$crM!uW}Z|vax}51RJ4O-{@p}AvJzVF@_9Q94&aU4s@1?+KH!%*f0wROyJ%78 zuI||SB&>M-fwmoL(8+b9e##_WJZ_n-yR$W6MmC_Pb#ETd7D(wkI3tXpm zfk~W!_4xzJex|t%BLJgt;EV@yOrQ-;58KG&aBf1zQA(|%>)b8wa^cputnuKglhm>b zZirguSVE?FWKh1>T5V7qWqYvEcNTiGi(nwtMoF}*(sAfpvvTy*!;Vu`5^6uNvG3s0 z*^StqO6V?d-Wf*S6FsAwW59O#~+?mPb zy_z<|Fzu{i&?r3D)rwwDrd^0)3(7&fJ%Z*Km{pNNc!H&FIGDPI1hO1Q*Id!w|Y^UMywY1EnsH5MKd( z2wwIkn7`)9Z(8jBCz@_NcHLqCRsfhQ%@M}1|j zPr#}VEtWZ~9VGLiE0!~5n<)60FZeuQV{{ls@B&0n<5O`+UC21@Xgd9G_^t5`?3rSLRxp-siJ%X#>RaxX=i{o`D~wO3&--@xe-bu&fO>uH+X(=KUa<@TzGS%$LXJK4kDrGa6%J zYBe5T5usqofIEXd@gV=N==wTHS9XR{tMKwx-=n|bYR*@uH@6yLJvG9Detbn_4NFh1 z^bD@Yhbr-*vNpuIOhjoP@_HQ22{ip^FlW=((BRs4!;n}UI2v$&)3?!FKzGbb_*pBT zH;jZtQH1iBr*}o_=SK;eQLOk03}pM-XG1w(R?b(Ika`@1Sfhz}ss(CQ^|-Gjpj-t( zv$vw|ugbDISS4Vv_JHrvc+appR3%{ip6KX$cGXGjb+WFPaB%OI;q39Vax4+%q`AG@ zeif+CG&kYb9*UXV4U6O>G7Be&zmDKxz)5?mT@b4BB7F~hm?PO77?7fpv_a05$+_Rj n8|C1)8Xl2AaW$I7PamWA$yQZKN=ksJ(JY#e(fg!H6$kqdi_vhb 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 92bacfba4e7a0450779318482fe5172dd7e536fd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4603 zcmc&&&uv<=t|Qh%k0LaB;SMT$geD_<7l-6RIbHZyBd zTO6V`YIA^!OO+mbY{XyDb1yNpN?7&Q_Q1_Fsf4(+@6D_?>%>b_0b&l%GjHbYzVE%6 zH{bf_L?T9@{qa*}`l~o0zvH0URIjsf3OdWgA{k;ymRykLdH7OjHK^m+RV6BDH? zSM8jWdWerTBv*Mq%j}$Zhmf*2*th|mWnz)UYhnMn2s|k-)d?_a{Eo; zn`s7&4cS$gU6^*`0VP9+bp6TM^d)9fmcEgnDa~gK*KC>=X%bmFec@cX%(GMCS<9C5 z>3NnGnPO@2K$a?3UClJ}#k^yhwI1O^s;TINH?b}d3#6fuA!AATRXebO$a4}(ulW(u zK>-mWSDQ0|UrQQ*(M1CBI(LDV$~JYXx>M3;U|$JmbSFP&r(%9sCaLXIXpxB!wybH! zo8qf!2Z$Aka&OTuox<55e$eo-wY?rR@fZ_zJ+NqF9|%C#MSAwM04~l2T_E`wFT%pA5 z-GOUa>Z9`$x&vL8mpX1&>ub#?R}Mcvd||np&Cks44qkAjj}Gg|({qPr+jEDyvFC^F zVo2@^rTN`K3oP`JDWS+>Uf#f}wx=~~pi1uoR{_?9l@%y9xMjArYoMc26-srxQWRqI z0k@H>5kskk<=?56ce%ZTE1OYr)fsBrMK?liYs!#mKe$c#sSG>H_>}G20d;SIB}2h$ zS6A;^e6SuL{N7p{O4o#u20-cOsx0qZ$wD*{j~P7v0~&Z~%t~Q5*uX zBB~58#T2;-7g6Z2FF`Dl2a55RqNoPc-e~tDSyhj0M4;EeUZeYk=rnbJzvGb%Jstw? zRC}iKHF)_A?7JXWSt8RwsTQ?1d?k9d8w|F!77l})=6`XRCGv%oiNlqtxgFx*u-Zfz zb@3MLBxNmk7#1K-WpDsZ<`17^j3>Gqfn6FbdKZjC)FPL$v{;05k>0s}&JW}1TNEQlS1Td&sZ zf%FLqqF`9|A1{#Kw8X8MJ5F6Y@{2aMo*Z6MZpGJ!Mv$`%?!55u+T?-2T^m+hAoNl# zq+B*hTnJrxI^zSjl7jb!-EoXZq}Jbr8;sLOV8`&}`K)E6-ZQeQjR=f>5@7}QImS@t zI(Xi8W=j_GFy`|x57`u*0DCVj^f>fA<|z__iuN8}#HAiQfL)|U6kBB%2dShk2&D2>b)nUgK%Y-Hn;e4|ZX7Gd@iiPic<{<=?_ zQ}Zn4(dLwwiIgxod3lS(b1J$bi&BS>b|#lQ=<)ll&;_-hgWU%1$OW?5O?s1ethMA= zJvsKHR8PKkzh`_^8{fH=pFNPbTLTH-Zl$92@*>%X(R-s^2ou7!W(@aS!b8v4K&aA&eqJ*t@3gudDkX$+9|*GXo9m!IvuXiKu$iTMfNN z?^ATg__|H7IL49kyKBmba)9pewvXswHMjzQb5N{dZd4Qqar!wJPT27~ivxV3oe<>e uBs~o}tYQp<*oa7y^nkqmCwYA{Iw=h-eY#2DXR{`yq+?6g-voZRQ~v@Y2@;S1 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 ef7c9cf1866b00442c7ee0eefcc958fcc2f3bc87..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3732 zcmc&%U2o$=6dgN`<0MYn-R%}ue5{3#P+o#SNJ!DLyV@cULR(fU%c3a4bv={Tt!rmz z9I8frYo&bwegixr{*hmK>R&)eaPN%mIBnf#pLQxwW->GJJ?GxJ=k`gb(<0FR{cAKn zZ4>f0PV#2J;4wVnGw3K85~@)>)DHEbu92?^tx@BQP=nP@YeNI(b!x)gWID|2w6R5c z*6h=#F`b6Y8OPjt7W)S*alV@*K^!?7UPMDCoFld))Iz+o&-|o+4sRnpEj6Bag84Yf zTMY(};St-=F*4M^D4lA<8r5m-j0_D{rv|N`kyCAG!q}t@Ic~t%qD?urVBDf@Ic~z( zrX4wM!MICTZWB>SsK#tXnbFVJd<_<{;pZX+WFH8)MS-K<6h7+kA@Qotf@#49{`d-fp=>v2w=>(-@4C3Wr1{S) z%m+-7^RpLDZv;`^nL{~W9ts|Rs~QR}FA3#E1r$Gw1zQfw!qx%|I{$jV*ctKi{QVM| zJByBy>r29^^2}baySE&g^3DPbW}NH6TpbT?%Oco2_P8uGTxu-y8V}40<3)n^oN7T-7VY?)nk{On(<%R1wlyVigkQw`h=5Yjd`i zn_s43$*Sd@r!3ivDGIhI1Umx(6^XJ(R|1SKycopz-Bw{s?UL^$ad=M?nLDslI92u1 zFY`BJsW1*WR7VOyWvviIBJm=hVHGuyz8!2U@CDzPFdhUii?XMGB>Gv|)6e+nt0ND$ z=f#(*H*|UHIAW^ufiyF#&VDR#H|9PlZ+HzheqCe(O5-@9Z;lT5X8~&38|mmMJ2O7K z5+8RJ$D+Tm<24BRZIKXv$~lYPT!5Va2W<&Px|yLFCHo~x)^kc0xAbhj2D|uO?AEcv z5>Tc*{x*&<98(i+X9DRE3o7nl_W^$0$x@up!Z1&BnCl>X-@{EZn+a 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 2c911e673d2b911dc00ab740825468049c2aa07b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3792 zcmc&%TW{k;6rOP$C(Wf7wp&VbBk5fyaMwoTT=5X%xkQ!=1VYdu%?>V zV7|;&)V$7C0l->pX*nmGTfQgd92`A`qunz=p0wc{`xbPGB%jeU;sSs0N@Max%}!cp zsPqUi@c8g{UNvgfsE|1pKM4QciP#l?Mv#cyIL;S&0TI8u_?LVkL$UImqb)R zg1>jSI^A8#h3q~K1_y^x_iHYXqj1#KYSCKx^68_1pL{s<20?eEv=S<9d80x1M0T}? zWLLjlwnwLN)pZBKAadO$#HvUzCo5XGcDmQ2u$eZT&yquqfSP+mk@M)O2uEB*r*;(D z2VgGxTXr-U@^&rbpd)xR7J<}!&74We75E{6Mtn&{8Ry7@9;z_QSi)DYzymG4HF+tK zUcc4F?F9hr761TLf&H`RkN+1&{$vW~Dgz_Xo{FI0)t8H*;PwJgekg#_^FzsRhh^qy z1_sryG&2`WBOzYh-OQo6GpiW2zW|&f%^ZR|+zw6tWCjK!QZbm+74ho(IV{(V47V46 zvQwZLKRme|lH9=z2)ar!kXoV@xcN~I$J(q;V7C~?yKwIeoEZrBt`j$OV9LqsCT zujj_^#vg=2DuG;EVqjUW$y8F=jw{-DWvd*QT5V}k)I$2H76lXz<>09Y7bOfM@P6~WGtpW;C;D474 zkOAL-56w06B-~1c@-d2|vbZ%_PXXjg3 zJ>(B8$j7gLi6MQS&Bn2hB_zJ!gV92*-c8?#4R4RIH#-M|5>LGcO>7^B2J9MA+~3w$zyK==+m!OSe) z!^h1ey!E*6ry)*xCI(ueJQG8w@;rPkCY?qZ6OKu1Cj3@)R{?grWvOq+o7etKF8p?SKMb~EeUwdXf8JHLt7XjBQb52knfr!_+U z!b!fFFnIu7ehdR6UBW0c0y;Fh1|?q;R$}HEVJ0t~mbxa)%glng#SNI3S!IVb?deC4 zLpBMx(+`ES9r{msd7$W>NT@(Rdx)=+X4 z3Op$n;P4fWmT!PQX+s(N7IcY5KA|VX1^dA(jp=7qCu!}m zC%mDfU*R$@^4iY#2iyq;GIGLxvLGE?X=SH6=d^p%)q1&Z?r_tHP053PT+2N?C!zux z{QdA@ySIJpA9!N8Ekd+(4}8M|_uEIEFF*gTH&#vvm2$)}@uM`4kq@hVr~E$RSwb7E!i%1iFm5h>o2oboL=S^tYU7FyyUj z2DvSGG!Y}I%`>+rwWYuh35?luD#|D$Y(3Z@Wk$hQufYSYy)}I)(O$pR#pNXc{7?V@ zP>q~lykPRbF!DQ7Fc%pZf#FjG1&_W`3g=5ynj7x1)d4Qqw68b zZOnk+RVfBiO}qjpKg{9SnD+^+7Q=WO&Yi$zhfbdov5x64o14O6#tx7OqyckUjg=4&U?%LdL8?tU5JmonFZju&L9mV?G>t zL+-k9)pdu8>p-r%?z4#(Br6ryWufo70&`|;kCTiifOAPsrwyFmz+n@IS@u#ADkCuV zEa>ue7;I`9wq?+2Y72H6O(jn;L}E2(V-%zcZ;Yq*>JT4xDr#SId=I53=xTdPEUm`E3fd6I6WLir7< zqgdRU-b?}H&9FpSbNexm4nl@RE;AxeV06Tr@Flo{ikRO*-DdHvLK~m$WZIM#Mu0Nj zDr|~WPQfDLRF_o0&c2OBM1LTlfYLY$tBAo!M&8KhSZKACkK4Q21ewh-k=z?R%Zj4b zk!&SJQ7f@hOCPzvJFCxBC30@4J17*^MI2sNN+#voPZ{#c%n$huOWNbNd3!(|9gf(G zGe{la38aIC5!uk{C8qFlrg&K!SGMMMENP41=WXFngy5qW7abS>Kr1%JYNB6ajl-NZ zHdAZNZ)w&A)_~$w9B$$88V;DF#BCff1Y-;CWCV%u9enaG4)5dRMv~rq9t3HMQ#aGW zE$U`s>QpxmKY*lDFC*cYv}(eyt-DIFou;L}9pAb1XCi-Z_TXQgz?2hP`YP#rq7w8h XCuF%cw(n_Ly^rZrC0x*;wEpS8xNC4U 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 c7b67a47a1f0c13005e723230136c818b9274c26..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2696 zcmbVO&u`>37>%7wCY?@yh<2gCN<;_=Mo1%ZS)mG{u69>&>aNrkhp1BI#-27Mi3i(h z)zS1q_lEYqH?}u8bK|=IMXsFq4-hB5&ofCg(=I|pitRY|XXm~5Jx8vs^&Ge!{PlG7 z5w!lmhxX;d!yerF0TkTfBy*x!O(Fu%G^06N)v2Ny+hqGXXygi1(jIc(#KkKooJ6x^9Q z@vJ0?1HMw>!9jGbPfY_AmSo|*Olmc>O@~Rikuct>Q6@>^z z7ILA-8Jn;iI*f$JFk>NL!om}&m`Yh_p$bJ2YH?f^TKK?&p>g^0X zh2K)Sg*>nP(j-`~8EJsRnrb)d-EbBKFvzl7`7`P)KeJ*DLa7xfkTM-W5p)C6Cmxx6 zejD7kiXp)*)fcuKxVPb9ijs5;LC~QEDT3mh{M;aOkQRxRJVTTL?tCogP=a<_eV`gZ zR-L6>sy4D?rdV#@%;misKA%Nt&uH@!el{*4!EsJl7O-Y6v|;&pFl=@Oj-XI+m4|N? zf-|6M6{={pl}SrzR;wu6c?R#mVC_NSch||}{w+jXLl<&=L9t)L{Hb#axm`NC2Q7jv zwQeYK*(uX^9HtQ1d_ zl1gnsFK}a-=!=Yvwcu9ZzzbkMe^};a1J&Dl|1m_-c)D!+0aoa$;!%(JZk7zveV=82)C8!qA1LwKRiT*2DiOl64@o#bg%$=IapsU`6ñg1OCn zXl&Wxq47Vfo6McX#YoUNv}9$J5646&kn^Z8AuwYO!4w_0S? zBfo%MSe64npqtdP{K>zuTu;1ZX_%9i>2+uz(^GOy2xKKPS-yhKhRlaHYto5j^ z`sTMHL`MTEi&C7)+Uwdb)#hyGYlS!mc$%YoH{5^Q+_uBrkO#j33VpbCuZ^XE%Q&u< zV?@Cw%O1?r=;rJG9sboCxIgIOL0vHTw}`7=jAwkAVY?s4KY)qM-Y7(`>Vs-cn&iTq zNN&#`yFsfi^&yN<_p!i3sa4p+3X=|h0rVymffEqg>5*VH=mi`74TWz9e$~z7@o`!l mTdS}-wf{3#opwwss$B#_SHV}aJ%5eATC>9t;%}38+7fK6rNrGWaHSF{E((0*b7Y{E^0*hD@2H&0!;xaL?mq`V__}cNwUG-HM8qb zOd_RK4pDnTij>?+B}7}%L!{n%<=RSiWHr`Gk&r45y&0ukIQ7l!kJtIB=y?5hX5P%3 z+4;Wr-uxJegbM8M(PUI?-^P?HL7Lq zVdG}rP&D<@=rt8T_$VhSYAVnAHP$cXmDB@0rQMyz8ADI84eM~o1Nn!hkWMSAVx-f> z_qU$?fTL=HE$APEm`ByV#G=0<9IE#A)AzxRc>ik0`rzo=;Ana9R%P&3DLz(?k5%Gh zRNYfz&n8So+nB?UoCnP^GTc)~Xxgne;xpGc%HiG?hRt!aXqGF2!@LXTbNnn1$a2M% zkZ6~icMS!rSj1UT#(F+06{Q?>7#cLitRwUgrC%9=h9at#d; zY!rw(Bmz?Z-;A@JgrCrJGJUG{zzi0Q41Gro%>aryRP>plI;Ttl1~K-TL5o^CH30TV z6y78ZqIh&W)Vng1PO!m$vj=s?mQs)JEZtcQZM5%Sx=?QKt+e+p3e~orm9NTeN7h?U zueF{ox1OoAo>>f5qx%*kq!kQgT)3b?>kRxWP+11Jyl!kzja_!50*rUwD$QtqDmY0Z>V;bAJhs1Hqig$(=b+JERfIvy- zhy@ka%wgS-a{0u7yDNySgvN8zPqm~4stKO#p*=00IHJ?=BOp%AUqZru7|-4dfW_*Z=nKUplNtp9Ow1& z*)558j`tF+PNiAi5bBnv-2@Mb8@#4|qnY!~`hZqJXDlO?f7VY^c=0EfRF^e%M#02j zp?)V?Nz|8V&ZM#5z7vVkbOBr&V29wpQ!;p0mSwb@wuAk8z<~VE8^uHuISAij28agv z#YzQ{38LlP7(?hem_7*lBm%WWz@k^XhcZs0FgutPn@Dc2Gt~LUG}wa6jYT=PlVup4 zF2;O0S>Wmr$Lv@nIKUj|)UyB_t$4*vaCriTqSMlCb-4L23VsO@Qmi<9>0(!7LpII`PZZ^o(f1TLkd2lnS=ljs(MjTxnorvR{cvO`AkXfJ|EJqCgWk><#{(6uIX zt;EYhq9PYBpWNAH|PTz+xY-z||FlUCreGDa&!^V^* zv)c&UKBGA~3M0q>h@TO&Y+YGsVj)yem)7S%%)7rD?+c7`HRO>Y6l|;U&4JDu0`b}h z&4rZJ(J2Iu)sQPXB7yxi{mrC^Xyke$4k3EwctgLgFWsn NU$g#ycG0pe{R?}Mtv3Jw 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 e4739d5e59ffcc8d0a9100dfbaf6d1eaade2d917..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3461 zcmbVOOK2QN5bc@$XtW>6`dCq9c{O$ z%u3N_1xnCiAt&R5bW1`4Nw5!w+;R&(g#@yTY*vf~F~pbL6v+_qDb+on9er#}(9FwpviI!Uk&OoIN21uZ>8 zbWJT_lIDB|(a#LPfBO~?`k6(P^4M(VNt+>;QAD$K?zq;w_j$nGclLlT*NkWZN&J|^ zS#<_Z>zZOnA?u)l$s8srYJe2MQCdsb&gODX z?VmUL_1m*JZ5sXTiUt_5FSlR`sg$azW-3*9bGL2#To$#D(FGM`NZ>DJL-=v$^m^u9l3|7EJ z{&+Mghqc#10Csp9A^q@$FFl)2s9Kt30h#&_9GT7Oj*}oLeQS`Kz-MGmRdnL_SxzQ$ zmT{c89Tq2TE&d&TkBhyzJY+h$N6c$*W)>T!oXtsn-mM@t5}JsuAmx%M3?-g4(3Pk! zMHuv}95(@@meC5Gd)(#Yw!^~g8x)7U#=yTOYukfnxZ zZg>C#a08l!es}vU2J4=wYuu%1EmbOBGW?t~y`(uCu1UKiH-h)qNh6iU?0)xvI0hw7 ztn!5~Jj428)$z6A0YK5k+p-};2jN)*gPb<=G7W9;ziOB`EBoE=hOFcB9olw8xf`DO z<8ZU99@I2loRL8>oVsJL{OBjG4?>xufcf8wd?07?_$nbfA=G&Q5eA4D&d50fD|Q4k z6gZO|VQe+hc%>+CxCOxJ&NA4yOX4V;V%!7*IEkUyfoim;674BR-=@iWv@$qa9=x&9 z+*v;S-dgjua_riRFlz5yS-87!@6)d)t34Mg@O$GzDfX8S@9Nr|iiybz!G7S9X|&VwNh^c%>=EVJJOn>@A4d#9<_bY?QV5 zkmHsStCeYS@Z_z!qV6}QKo`7hY~*>1to|7EFculC)L1P(1iF%i?$_bel^O&^#uyNW z)vmXyiJ?kjXf1KNEF5~?)m`p6x7PJeS?GM8I95%ZuE6@4k>6&1&-{{kocIuAo{6U( zD%G>275IsxW#RC8V|Q8T-dm2mJa`Nadlg7)faDM@IQdl&_K{WaHRj7sxc!QEoxSv~L8h0t&S!MD$ zYI2rH_Ng*H?j=xZGy`ECsgGBM_KMKH+Vw<`m{rH52z@tdGCXxtRwR|m>PkLC$FWrE zV@SC+ha3PERt$o~RMS~(&gcqzk+3T>Fq4xYLHdCFfS6|c*+NSTp@dczV*to9+TsJD zNp3TO!p(p4^`W-y0E{;2Xsb;K4Q#c>Lx;8!@lelpA{;tlE2Ao;uvM4Qr>9kIn&n;= zIP3w=!i60}=!>Uz{!8J!{XrMV8DP*%W*i2x8Q?hXDQb9%qJN;F3L5$+e2L>uY#~b7 G_J08uAZVQc 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 bb26b23e6469064a74f979ab1732b5873029ec34..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2651 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 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 1d5ad67cbf97079299d39d95ef7b7f36f83ad8b2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2471 zcmb7G%WmT~6eT5DmK{6E%mnQ`I*3pdXbluD`Tz<9MPJjR=wi|o$)>6SLJ{eSJ(A^; zbdw0#Wq?`rAChH$N!zYwJ-e=YFD=Qc?X*)Ge2JIjIhTjLPP^SUf%IYa@i1u;@+Wrc z$$`$-kkw~Ua1szsxfRoZn%W91sO{KJoWP;v2f`iRctLnWIG1+N;MO7Ox!I>rQ$CA@ zKTM^6kVemi_J5k{Q9ALDMu|8)pG5wtI9BtK^mr^H-G2q!k^LUcoo_=WBJ9OELt2+kwG{e4Nlcye)4Ky_6AY(r{~fI0v*W8gJP4q zv<<)PPD!I*Qd@B)`r!%W*SNw38Ka|Ub_jUDFZ$fqss9XgIH&7Zbo=eLU!*-L^h{3F zo_};A))c;y^6h1J-i5EPEhtuXn9{T422^>gBD#cBBCv;$6>inGSNw@eLIesKYVhY3pcq@% zh=OK6-{MyuYJpq+ms=NhrB{1)?v-Ej?og)50y<({>+Y?>k>$i)1dN0>-R%Cdw@afH zs-<+~Hq2Z{ZgCBaaBd~HVpoy_$+T|eu$W~o+52dC4~iOtC6im_B6pC1l|}y)$zr;} zUz7YalwsnJCfZL%lbIHU_?IStK{?*9uFJlBU^bfzty#=pmKjRyBgk`^xhuZMaARbK z$nn~0I);~SY;*bteH$;amHo0Twi>+hP~YTC7PjTN^r(CU`}Q1pA1e746!R;?MXW=K z;MeNz-U0zDSN$A}4a*%jd#BRg8pGRtfcn$LMZ2=ENpabJ{TKe#C3zDE)Ql?+I1>H^ zklan46TOJ9BL2m=L)15q-A^O_7LWn7(+SF|J5_ zrcIc{3#s0P!XpmtSeDzSPP0ud2Qt*Ajs50bYV3Qid=KZMyNptT-KCh;KUMuAruC2E iPmIKu$jq&QFmOz)@V=Nc{}SIHg%7<$Jj(+tJNDo0{A>IG 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 0f1d11fe97ff308dee57e781b434f29c23cb735b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3533 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 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 02aa6eb6081d9cdc04708f1d77b89854372041dc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2438 zcmb7G&2QT_6elHFmK{4u*J4|{713?;MFZ9W1A<{I+5tPY>yT`~Gy{Yp(yblI(nxw~ zjNDS7r~MDfaoB&P>rVX_h8^}E_2G}&T_%9!llVyfe(!@%kNW*Cf#;t;hoiJj$Y1zU zKMn{Fp{XyR<0K@Uax0-BHKP?;FxrWoI-x_!cZ55<^@{M8a4zkz#jPVUaOV%6WPFwg zf0Rl8Sr(rP?SG%^aW?gj$Ei3vpT_=~I8o(Hb}|vM4&K0Vw63gcLc!b@ zgxT7;a;^zTU{dH^P5^=ZH1u?3z&E(fpk1`oEEiJZW`)PtcskaMg?p@q%GfkYMd9GO z;-jU{O32@YWcXd_D9wv|OOxVdK9&)B%qXB<@M}b{LD1J7a&4zaPej!yjQIHT-G( zJR5#1)Tz$$;Yp^4xp8i0ydLG_;h7qij#fi+B*Z|FpGy~T_n;{cIzo4;OS^P_t41Hx zj8?3N)F0y_Tzx?%Xd5~|dL}b4g+BLn=0As!D;fF?!=T&stLIP(J(E*)=pVlj8x(83 z0hI_0222=er z8Amp16C22t5}akg6`h9Y8cw+jTi=6LLbF$FiGgFzMl!i8`43xc$|EiC zxc@TgLa(u?U3m4^qCb*Zx`2%kwwQXaGGe{-prPZ=KU}wTt+YaPbI`m+a1r??aJCYB zORphFW#=6{7VAuAeX^0qn$iseCSDDg#U_TyQt;=Ij8cC*)qXmj&a|k~U~K^?)bl}; zI2d$>UKMohYKQfZyA8ZPTX0+P1XdUma(6($^wo3%m)ykW^mqCJCbKpFc|Gu!z}4=( ziEkFVOa8%g2ad#SmHW_@sl!F0BZO=MytJXot_ p!I=uGN*0{JpNhmA+^k(3TS}%zcwd!VzsCE=R5{1;sAu);e*sytX)FK$ 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 b011954a4aaa737a78757a6d86116586afdaa3cc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2498 zcmb7GTW{Mo6ecBEmK{4u*KX~)715qnqnHP50|o@cZW%BPL$eG?3rsUWC??a$u`VO& zr7`lB0)5(lNFMWt_Mq22^)Kvc=g^X@+0NDqAbBVrQr~wjJbKdYwh26s{u~^|O+x;{ zN&RtP@-;N|8FZWkgi~&fX+X_r1s06<*p8jRq2znQ9o~3BctbcBcF^F~KIysBPamgz zG8X<(D*e55cp|j_Q>LRd@eiU{?4Kn=|5O~R*-Cmi5<}g81)n3kJ(@e;hDr=^R)6eb zqMm~w2*4o-C9q$TBLK+l7hd3q#thB{4P4=ln%qTjX4c?M?itwwtrl+^xg}blwN}j< zyu-U@r43r^e8b3VFNs5f4p7_7y^_K7lbvy>)EjXe(i$a{krg)e5IRjXJtr5|rFBIp znEjkETRWG|6#)rM3%$z;ps;rXPZx%Kg~#-oc|%PyAtfG`dyGX%q!|k~SP7T0B#cGw z;J*C5+G{1`??N(suXGq^`Tg3Wc%DTvM9&$8Yw(T|<0BYbj17@LW7p*Gr#st&CvcD& z{Fp?~j`iR>p-yy~4Q9@t`Tm=|9};o;Q5;6eATzE{O-zJYG&og*Ss18+0Rj=y&(5R^ zY&y`C2OXj7)TM1Yy;XARmpqs3ibmPNjd;R28KKqabbn8#U?hF!>(qY+K{?~@SKRyU zwqHEkQs{|H)V6AUIEhIm*Q;&=qAMw+MXjOHKrMJs z#%9>yN6s-WAUc4NHQjnF^l{3Ok6OiNS8l?Vo+Z~{$XgZB1*Gy8EZ>JV17|PU6T`|N zK!zIZc?l>+))k_l)1Pm!D-X56PyU}-=X#}8dv@-XZ}aYvOyfCp#JbYl8-*dujawBV zcY1Hp+NIJG)zzW1L~))OCVXzBxTIH7gurRt$Yj3D~*n#VYOl`Z!;E~e+iH~jQutV$HhvM zF`f zk2+SzwP?HP(6)nPA)C>nM!VbAnxc(vh;YhsI&E6UW1IE-2 U?~57lFY!Jy_0O?9>RBE8A0?!6H2?qr 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 c89d48d2c118c9f10425ec59e64d55a8a29a3bd0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2481 zcmb7GTW{Mo6ecBEmK{4u*KX~)715qH1A~TLfC0gP&}l*?_78k^!r@`&xiTPy<8OZSx&1Z@nNE@q0jRw{R*3`6myfAj@8hV@Mm6P)1hRvJaqBMA36{VO$zlgo4@6 z2{V;-XJ;bVsJs#t*Lt5u3J9)QmDC{W&7^Y ziP)f6>0MKa;3Vdd+^)MFkc!<^Q@#OjGXpwe=Wo|6R<%N{fm-mOj7_t_kDOx|KrjF! zBj0;0)JejTPqvBAQ0~H(fg!hGC^|LK6{PYOEZ>K=0B5e*6Qf8YK!ytJc?l?{#ucKV z`Oi1ll?O`TC;!i^OTEgf1G8|dw?%&}lXwXov2HZ?PHD($<6ce3&hM>SyHZ-Cx;b># zC@vF0htG`^*Yqli5ICJ1nJjndG<$y|lNF`c2%GTVVY56)b7>?&8pt4aCo|>7li6H} zG96YnfI_wI)d@6|ZvpBm^xEbA)xf(8Iv*|Bt$72Bj}E*$AYdXZI)!_0YI6D~y^CpW z1|dWh~ae4j}g!`+XindDMHIpI<(-X{M&iY-izX&#=0fLC%jnq#?Y1r2wQwwD zJsMPN4{Z4!?nGnhIPv#ZYMghPd1a0Bj^T$!;;pXtHXvv)x^_5UEO>v3_mQrDmf=vx H=$Zcj2EuBC 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()) - - From e528e28bb8dff76a01636816a3b06eb8509a2213 Mon Sep 17 00:00:00 2001 From: Six <82069333+sixtysixx@users.noreply.github.com> Date: Thu, 19 Jun 2025 00:43:38 -0600 Subject: [PATCH 2/8] Delete .DS_Store ew mac --- .DS_Store | Bin 6148 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index d49d7754541bf1d9940550be7ac0eb32dcdb2fd2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK%}T>S5T0$TO())(?gd>&_Z zH)5&cK@cf3F#FBU&o29I*v$ff=uG>201g0HsDu?8n=gdoq)SpU9zvnU7(fORM%czn z+3fg_4A97>T2NGKvxzVdiTzO5#c8d2gapt*x!st-95)Zh{Ay z2U(a+C!KJ7MZI$=lc==A=pr8EJ!k7grdb%LgRv@z`vXk5zKqkp%sX4*duR9ftT#*^Q~9i^75Eh@*)Ukb3mQMH@+lan ziA?V?vmB+YW@HAK0cK#;7%=CY)mXKK@p_m6X5iNh(D~q^68Z)Ujq2#YhJGI@ULzzy zo8A(Hu0h{mp%Eh}LYE@yQemDLLYJf8HF>_lLZdDRp=QQ$%*?{PP=uNt{jN#};cMiU z8DIt$8K~;6L-+sb_wWD3BJME*%)m-9AZp#9+rcH7yLD-Cbl1A5x2PnPS7`hwK|@`| gm`hi26IBWNT`~}TgM~))pzx1?qJbM`;7=KN2g3bQM*si- From 8b48f00b1b0f9fcafb6afff86e8757ce7e164994 Mon Sep 17 00:00:00 2001 From: Six <82069333+sixtysixx@users.noreply.github.com> Date: Thu, 19 Jun 2025 00:43:56 -0600 Subject: [PATCH 3/8] Delete test/client_test_1.py --- test/client_test_1.py | 62 ------------------------------------------- 1 file changed, 62 deletions(-) delete mode 100644 test/client_test_1.py 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 From 897255bf82535ac90239f09e704a0d277078d1b4 Mon Sep 17 00:00:00 2001 From: sixtysxx Date: Thu, 19 Jun 2025 01:43:18 -0600 Subject: [PATCH 4/8] removed unused files --- README_ASYNC.md | 420 ------------------------- README_ENHANCED.md | 521 -------------------------------- enhanced_performance_report.txt | 26 -- performance_report.txt | 19 -- 4 files changed, 986 deletions(-) delete mode 100644 README_ASYNC.md delete mode 100644 README_ENHANCED.md delete mode 100644 enhanced_performance_report.txt delete mode 100644 performance_report.txt 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/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/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 From a85c1036e5a383430265741d857d0e63dba51eff Mon Sep 17 00:00:00 2001 From: sixtysxx Date: Thu, 19 Jun 2025 01:52:18 -0600 Subject: [PATCH 5/8] `ruff format` --- advanced_testing_suite.py | 441 ++++---- comprehensive_demo.py | 446 +++++---- connection_keep_alive.py | 366 ++++--- connection_monitor.py | 628 ++++++------ demo_enhanced_api.py | 171 ++-- examples/async_examples.py | 172 ++-- load_testing_tool.py | 531 +++++----- migration_guide.py | 196 ++-- performance_tests.py | 296 +++--- pocketoptionapi_async/__init__.py | 27 +- pocketoptionapi_async/client.py | 842 +++++++++------- pocketoptionapi_async/config.py | 62 +- .../connection_keep_alive.py | 174 ++-- pocketoptionapi_async/constants.py | 308 +++--- pocketoptionapi_async/exceptions.py | 14 + pocketoptionapi_async/models.py | 59 +- pocketoptionapi_async/monitoring.py | 318 +++--- pocketoptionapi_async/utils.py | 236 ++--- pocketoptionapi_async/websocket_client.py | 418 ++++---- setup.py | 6 +- test.py | 19 +- tests/client_test.py | 7 +- tests/enhanced_test.py | 241 ++--- tests/integration_tests.py | 938 ++++++++++-------- tests/test_async_api.py | 229 ++--- tests/test_balance_fix.py | 47 +- tests/test_candles_fix.py | 84 +- tests/test_complete_order_tracking.py | 122 ++- tests/test_complete_ssid.py | 90 +- tests/test_demo_live_connection.py | 48 +- tests/test_demo_live_fix.py | 79 +- tests/test_fixed_connection.py | 75 +- tests/test_new_api.py | 124 ++- tests/test_order_fix.py | 32 +- tests/test_order_logging_fixes.py | 79 +- tests/test_order_placement_fix.py | 70 +- tests/test_order_tracking_complete.py | 186 ++-- tests/test_order_tracking_fix.py | 68 +- tests/test_persistent_connection.py | 160 +-- tests/test_ssid_formats.py | 86 +- 40 files changed, 4656 insertions(+), 3839 deletions(-) diff --git a/advanced_testing_suite.py b/advanced_testing_suite.py index 2ddf2e5..83f59cd 100644 --- a/advanced_testing_suite.py +++ b/advanced_testing_suite.py @@ -8,10 +8,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 +19,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") - + tests = [ ("Connection Stress Test", self.test_connection_stress), ("Concurrent Operations Test", self.test_concurrent_operations), @@ -40,168 +39,173 @@ 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}") 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'}") - + 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}") - + return self._generate_test_report() - + async def test_connection_stress(self) -> bool: """Test connection under stress conditions""" logger.info("๐Ÿ”ฅ 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...") - + 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...") - + 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...") - + 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") - + # 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") - + # Test connection after disconnect await client.disconnect() try: @@ -209,219 +213,238 @@ async def test_error_handling(self) -> bool: logger.warning("Expected error for disconnected client didn't occur") except Exception: logger.info("โœ… 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...") - + 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("๐Ÿ“ˆ 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...") - + 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...") - + 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...") - + 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...") - + 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,141 +453,161 @@ 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...") - + 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") - + test_suite = AdvancedTestSuite(ssid) - + logger.info("๐Ÿ”ฌ 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("=" * 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"Success Rate: {summary['success_rate']:.1%}") logger.info(f"Total Duration: {summary['total_duration']:.2f}s") - + # Performance metrics - if report['performance_metrics']: + if report["performance_metrics"]: logger.info("\n๐Ÿ“Š 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}") - + return report - + except Exception as e: logger.error(f"โŒ Test suite failed: {e}") raise @@ -572,11 +615,11 @@ async def run_advanced_tests(ssid: str = None): 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..9f5cc87 100644 --- a/comprehensive_demo.py +++ b/comprehensive_demo.py @@ -6,58 +6,56 @@ 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("=" * 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("โ€ข 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(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!") - + # 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)") - + except Exception as e: logger.info(f"โ„น๏ธ Demo connection attempt: {e}") - + logger.info("โœ… Complete SSID format is fully supported!") @@ -65,99 +63,105 @@ async def demo_persistent_connection(): """Demo: Persistent connection with keep-alive""" logger.info("\n๐Ÿ”„ 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...") - + # Method 1: Using AsyncPocketOptionClient with persistent connection logger.info("\n๐Ÿ“ก 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!") - + # 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...") 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)") - + except Exception as e: logger.info(f"โ„น๏ธ Demo persistent connection: {e}") - + # Method 2: Using dedicated ConnectionKeepAlive manager logger.info("\n๐Ÿ›ก๏ธ 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"๐ŸŽ‰ 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 + 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) - + + 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!") - + # Let it run and show automatic ping activity logger.info("๐Ÿ“ 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 + events_count["pings"] += 1 logger.info(f"๐Ÿ“ 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"๐Ÿ“Š 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)") - + except Exception as e: logger.info(f"โ„น๏ธ Demo keep-alive: {e}") - + logger.info("\nโœ… Persistent connection features demonstrated!") logger.info("โ€ข Automatic ping every 20 seconds (like old API)") logger.info("โ€ข Automatic reconnection on disconnection") @@ -171,65 +175,71 @@ async def demo_advanced_monitoring(): """Demo: Advanced monitoring and diagnostics""" logger.info("\n๐Ÿ” 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...") - + 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']}") - + 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!") - + # Let monitoring run and collect data logger.info("๐Ÿ“Š 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"๐Ÿ“ˆ 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...") 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']: + + if report["recommendations"]: logger.info("๐Ÿ’ก Recommendations:") - for rec in report['recommendations'][:2]: # Show first 2 + 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)") - + except Exception as e: logger.info(f"โ„น๏ธ Demo monitoring: {e}") - + logger.info("\nโœ… Advanced monitoring features demonstrated!") logger.info("โ€ข Real-time connection health monitoring") logger.info("โ€ข Performance metrics collection") @@ -243,53 +253,61 @@ async def demo_load_testing(): """Demo: Load testing and stress testing""" logger.info("\n๐Ÿš€ 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...") - + 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"๐Ÿ“‹ 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("โœ… 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") - + 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:") 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("โ€ข Concurrent client simulation") logger.info("โ€ข Performance benchmarking") @@ -302,18 +320,20 @@ async def demo_error_handling(): """Demo: Advanced error handling and recovery""" logger.info("\n๐Ÿ›ก๏ธ Demo: Advanced Error Handling and Recovery") logger.info("=" * 50) - - ssid = r'42["auth",{"session":"demo_error_handling","isDemo":1,"uid":0,"platform":1}]' - + + ssid = ( + r'42["auth",{"session":"demo_error_handling","isDemo":1,"uid":0,"platform":1}]' + ) + logger.info("๐Ÿ”ง 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") - + # Test 1: Invalid asset handling logger.info("\n๐Ÿงช Test 1: Invalid asset handling") try: @@ -321,49 +341,53 @@ async def demo_error_handling(): logger.warning("No error raised for invalid asset") except Exception as e: logger.success(f"โœ… Invalid asset error handled: {type(e).__name__}") - + # Test 2: Invalid parameters logger.info("\n๐Ÿงช 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"โœ… Invalid parameter error handled: {type(e).__name__}" + ) + # Test 3: Connection recovery after errors logger.info("\n๐Ÿงช 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"โœ… Connection still works after errors: ${balance.balance}" + ) else: logger.info("โ„น๏ธ Balance retrieval returned None") except Exception as e: logger.warning(f"โš ๏ธ Connection issue after errors: {e}") - + await client.disconnect() - + else: logger.warning("โš ๏ธ Connection failed (expected with demo SSID)") - + except Exception as e: logger.info(f"โ„น๏ธ Demo error handling: {e}") - + # Demo automatic reconnection logger.info("\n๐Ÿ”„ 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) - + + 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") @@ -371,10 +395,10 @@ async def on_reconnected(data): await keep_alive.stop_persistent_connection() else: logger.warning("โš ๏ธ 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("โ€ข Graceful handling of invalid operations") logger.info("โ€ข Connection stability after errors") @@ -387,18 +411,18 @@ async def demo_data_operations(): """Demo: Enhanced data operations""" logger.info("\n๐Ÿ“Š 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...") - + try: client = AsyncPocketOptionClient(ssid, is_demo=True) - + success = await client.connect() if success: logger.success("โœ… Connected for data operations demo") - + # Demo 1: Balance operations logger.info("\n๐Ÿ’ฐ Balance Operations:") balance = await client.get_balance() @@ -408,22 +432,24 @@ async def demo_data_operations(): logger.info(f"โ€ข Demo Mode: {balance.is_demo}") else: logger.info("โ„น๏ธ Balance data not available (demo)") - + # Demo 2: Candles operations logger.info("\n๐Ÿ“ˆ 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:") try: @@ -431,40 +457,42 @@ async def demo_data_operations(): 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:") - + 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 "โŒ" logger.info(f"โ€ข {asset}: {status} {count} candles") - + await client.disconnect() - + else: logger.warning("โš ๏ธ 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("โ€ข Comprehensive balance information") logger.info("โ€ข Multi-asset candle retrieval") @@ -477,110 +505,114 @@ async def demo_performance_optimizations(): """Demo: Performance optimizations""" logger.info("\nโšก Demo: Performance Optimizations") logger.info("=" * 50) - + ssid = r'42["auth",{"session":"demo_performance","isDemo":1,"uid":0,"platform":1}]' - + logger.info("๐Ÿš€ Demonstrating performance enhancements...") - + # Performance comparison performance_results = {} - + # Test 1: Regular vs Persistent connection speed logger.info("\n๐Ÿ”„ 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}") - + # Test 2: Message batching demonstration logger.info("\n๐Ÿ“ฆ 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}") - + # Test 3: Concurrent operations logger.info("\nโšก 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}") - + # Summary logger.info("\n๐Ÿ“Š Performance Summary:") if performance_results: @@ -588,7 +620,7 @@ async def operation_batch(): logger.info(f"โ€ข {category.title()}: {metrics}") else: logger.info("โ€ข Performance metrics collected (demo mode)") - + logger.info("\nโœ… Performance optimizations demonstrated!") logger.info("โ€ข Connection pooling and reuse") logger.info("โ€ข Message batching and queuing") @@ -601,10 +633,10 @@ async def demo_migration_compatibility(): """Demo: Migration from old API""" logger.info("\n๐Ÿ”„ Demo: Migration from Old API") logger.info("=" * 50) - + logger.info("๐Ÿ—๏ธ Migration compatibility features:") logger.info("") - + # Show old vs new API patterns logger.info("๐Ÿ“‹ OLD API PATTERN:") logger.info("```python") @@ -614,7 +646,7 @@ async def demo_migration_compatibility(): logger.info("balance = api.get_balance()") logger.info("```") logger.info("") - + logger.info("๐Ÿ†• NEW ASYNC API PATTERN:") logger.info("```python") logger.info("from pocketoptionapi_async.client import AsyncPocketOptionClient") @@ -623,7 +655,7 @@ 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") @@ -634,7 +666,7 @@ async def demo_migration_compatibility(): logger.info("โ€ข โœ… Event-driven architecture") logger.info("โ€ข โœ… 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") @@ -646,17 +678,19 @@ async def demo_migration_compatibility(): 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.warning( + "โš ๏ธ Using demo SSID - some features will have limited functionality" + ) + logger.info("๐ŸŽ‰ 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,26 +699,28 @@ 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}") - + # 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!") @@ -692,7 +728,7 @@ async def run_comprehensive_demo(ssid: str = None): 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("The enhanced PocketOption Async API is now ready with:") logger.info("โ€ข โœ… Complete SSID format support") @@ -703,7 +739,7 @@ async def run_comprehensive_demo(ssid: str = None): logger.info("โ€ข โœ… Robust error handling") logger.info("โ€ข โœ… Modern async architecture") logger.info("") - + logger.info("๐Ÿ“š NEXT STEPS:") logger.info("1. Replace demo SSID with your real session data") logger.info("2. Choose connection type (regular or persistent)") @@ -711,23 +747,23 @@ async def run_comprehensive_demo(ssid: str = None): 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("โ€ข 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! โœจ") 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..f87e410 100644 --- a/connection_keep_alive.py +++ b/connection_keep_alive.py @@ -4,96 +4,104 @@ """ 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...") - + 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( + "โœ… Persistent connection established with keep-alive active" + ) return True else: logger.error("โŒ Failed to establish initial connection") return False - + except Exception as e: logger.error(f"โŒ 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...") - + 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 +109,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") - + 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"๐Ÿ”Œ 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 +145,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,101 +161,105 @@ 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}) - + await self._emit_event("connected", {"url": url, "region": region}) + return True - + except Exception as e: logger.warning(f"โš ๏ธ 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: 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...") - + # 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") - + 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...") - + 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 - + self.connection_stats["last_ping_time"] = datetime.now() + self.connection_stats["total_messages_sent"] += 1 + logger.debug("๐Ÿ“ Ping sent") - + await asyncio.sleep(self.ping_interval) - + except ConnectionClosed: logger.warning("๐Ÿ”Œ Connection closed during ping") self.is_connected = False @@ -253,32 +268,31 @@ async def _ping_loop(self): logger.error(f"โŒ 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...") - + 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)") continue else: await asyncio.sleep(1) - + except ConnectionClosed: logger.warning("๐Ÿ”Œ Connection closed during message receive") self.is_connected = False @@ -287,53 +301,61 @@ async def _message_loop(self): logger.error(f"โŒ 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...") - + while self.should_reconnect: try: await asyncio.sleep(30) # Check every 30 seconds - + if not self.is_connected: logger.warning("๐Ÿฅ 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 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") self.is_connected = False - + except Exception as e: logger.error(f"โŒ Health monitor error: {e}") - + async def _reconnection_monitor(self): """ Monitor for disconnections and automatically reconnect (like old API) """ logger.info("๐Ÿ”„ 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...") - + 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"๐Ÿ”„ Reconnection attempt {self.current_reconnect_attempts}/{self.max_reconnect_attempts}" + ) + # Clean up current connection if self.websocket: try: @@ -341,63 +363,73 @@ async def _reconnection_monitor(self): except: 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 - }) + 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"โŒ 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"โŒ 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}") - + 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') - + message = message.decode("utf-8") + logger.debug(f"๐Ÿ“จ 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() + self.connection_stats["last_pong_time"] = datetime.now() logger.debug("๐Ÿ“ Pong sent") return - + # Handle authentication success (like old API) if "successauth" in message: logger.success("โœ… Authentication successful") - await self._emit_event('authenticated', {}) + 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}") - + 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 + self.connection_stats["total_messages_sent"] += 1 logger.debug(f"๐Ÿ“ค Sent: {message[:50]}...") return True else: @@ -407,13 +439,13 @@ async def send_message(self, message: str) -> bool: logger.error(f"โŒ 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: @@ -425,86 +457,92 @@ async def _emit_event(self, event: str, data: Any): handler(data) except Exception as e: logger.error(f"โŒ 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' + return "UNKNOWN" except: - return 'UNKNOWN' - + 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}") - + async def on_reconnected(data): logger.success(f"๐Ÿ”„ 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) - + + 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( + "๐Ÿš€ 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"๐Ÿ“Š 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") - + finally: # Clean shutdown await keep_alive.stop_persistent_connection() diff --git a/connection_monitor.py b/connection_monitor.py index 4ef4471..f147c43 100644 --- a/connection_monitor.py +++ b/connection_monitor.py @@ -7,7 +7,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 +15,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 +33,7 @@ class ConnectionMetrics: @dataclass class PerformanceSnapshot: """Performance snapshot""" + timestamp: datetime memory_usage_mb: float cpu_percent: float @@ -45,168 +45,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...") - + 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"โœ… Monitoring started (connection time: {connection_time:.3f}s)" + ) return True else: self._record_connection_metrics(0, "FAILED") logger.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}") return False - + async def stop_monitoring(self): """Stop 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") - + 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...") - + 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}") - + 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 +219,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}") - + 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}") - + 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 +349,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: @@ -361,244 +374,287 @@ async def _emit_event(self, event_type: str, data: Any): handler(data) except Exception as e: logger.error(f"โŒ 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 + self.message_stats["connected"] += 1 logger.info("๐Ÿ”— Connection established") - + async def _on_disconnected(self, data): self.total_messages += 1 - self.message_stats['disconnected'] += 1 + self.message_stats["disconnected"] += 1 logger.warning("๐Ÿ”Œ Connection lost") - + async def _on_reconnected(self, data): self.total_messages += 1 - self.message_stats['reconnected'] += 1 + 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") - + 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}") 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,27 +664,27 @@ 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("=" * 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(f" Messages: {stats['total_messages']}") @@ -636,33 +692,33 @@ async def _display_loop(self): 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: + 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: + 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']: + if stats["message_types"]: print("๐Ÿ“จ Message Types:") - for msg_type, count in stats['message_types'].items(): + 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,75 +726,79 @@ 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") - + # 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']}") - + 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") - + except KeyboardInterrupt: logger.info("๐Ÿ›‘ 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("=" * 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}") - + # Export metrics metrics_file = monitor.export_metrics_csv() logger.info(f"๐Ÿ“Š Metrics exported to: {metrics_file}") @@ -746,11 +806,11 @@ async def on_stats_update(stats): 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..692988b 100644 --- a/demo_enhanced_api.py +++ b/demo_enhanced_api.py @@ -6,16 +6,15 @@ 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("=" * 60) print("Demonstrating all enhancements based on old API patterns:") @@ -29,189 +28,195 @@ async def demo_enhanced_features(): print("โœ… 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("๐Ÿ”‘ Using complete SSID format:") print(f" {ssid[:80]}...") print() - + # Demo 1: Basic Enhanced Client print("๐Ÿ“‹ Demo 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("โœ… 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...") try: await client.connect() if client.is_connected: print("โœ… Connected successfully!") - + # Show connection stats stats = client.get_connection_stats() print(f"๐Ÿ“Š Connection Stats: {stats}") - + else: print("โ„น๏ธ Connection failed (expected with test SSID)") except Exception as e: print(f"โ„น๏ธ Connection error (expected): {str(e)[:100]}...") - + await client.disconnect() - + except Exception as e: print(f"โ„น๏ธ Client demo error: {e}") - + print() - + # Demo 2: Persistent Connection Features print("๐Ÿ”„ Demo 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}") + events_log.append( + f"CONNECTED: {datetime.now().strftime('%H:%M:%S')} - {data}" + ) print(f"๐ŸŽ‰ 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}") + events_log.append( + f"RECONNECTED: {datetime.now().strftime('%H:%M:%S')} - {data}" + ) print(f"๐Ÿ”„ 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) - + + 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") - + # Monitor for 30 seconds to show keep-alive behavior print("๐Ÿ“Š 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" ๐Ÿ“ˆ [{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):") for event in events_log: print(f" โ€ข {event}") - + else: print("โ„น๏ธ Persistent connection failed (expected with test SSID)") - + except Exception as e: print(f"โ„น๏ธ Persistent connection error: {str(e)[:100]}...") - + await persistent_client.disconnect() - + except Exception as e: print(f"โ„น๏ธ Persistent demo error: {e}") - + print() - + # Demo 3: API Features with Real Data (if available) print("๐Ÿ“Š Demo 3: API Features and Data Operations") print("-" * 50) - + 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 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") - + # Test balance try: balance = await live_client.get_balance() print(f"๐Ÿ’ฐ Balance: ${balance.balance:.2f} {balance.currency}") except Exception as e: print(f"โ„น๏ธ 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") - + # Test DataFrame conversion df = await live_client.get_candles_dataframe("EURUSD_otc", "1m", 5) print(f"๐Ÿ“Š DataFrame shape: {df.shape}") except Exception as e: print(f"โ„น๏ธ Candles test: {e}") - + # Test health monitoring health = await live_client.get_health_status() print(f"๐Ÿฅ Health Status: {health}") - + # Test performance metrics metrics = await live_client.get_performance_metrics() print(f"๐Ÿ“Š Performance Metrics: {metrics}") - + await live_client.disconnect() - + except Exception as e: print(f"โŒ Live demo error: {e}") else: print("โ„น๏ธ Skipping live demo - requires real SSID") - print(" Set environment variable: export POCKET_OPTION_SSID='your_complete_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("=" * 60) - + print("๐Ÿ—๏ธ ARCHITECTURE IMPROVEMENTS:") print(" Old API: Synchronous with threading") print(" New API: Fully async/await with modern patterns") print() - + print("๐Ÿ”Œ CONNECTION MANAGEMENT:") print(" Old API: Manual daemon threads + run_forever()") print(" New API: Persistent connections with asyncio tasks") @@ -219,7 +224,7 @@ def show_api_improvements(): print(" โœ… Health monitoring and statistics") print(" โœ… Graceful reconnection handling") print() - + print("๐Ÿ“ก MESSAGE HANDLING:") print(" Old API: Basic message processing") print(" New API: Optimized message routing with caching") @@ -227,7 +232,7 @@ def show_api_improvements(): print(" โœ… Event-driven callbacks") print(" โœ… Type-safe message models") print() - + print("๐Ÿ›ก๏ธ ERROR HANDLING:") print(" Old API: Basic try/catch with global variables") print(" New API: Comprehensive error monitoring") @@ -235,7 +240,7 @@ def show_api_improvements(): print(" โœ… Retry mechanisms with backoff") print(" โœ… Health checks and alerting") print() - + print("๐Ÿ“Š DATA MANAGEMENT:") print(" Old API: Basic data structures") print(" New API: Modern data handling") @@ -243,7 +248,7 @@ def show_api_improvements(): print(" โœ… pandas DataFrame integration") print(" โœ… Automatic data validation") print() - + print("๐Ÿ”ง DEVELOPER EXPERIENCE:") print(" Old API: Manual setup and configuration") print(" New API: Enhanced developer tools") @@ -252,10 +257,10 @@ def show_api_improvements(): print(" โœ… Comprehensive testing") print(" โœ… Performance monitoring") print() - + print("๐ŸŽฏ USAGE EXAMPLES:") print() - + print(" OLD API STYLE:") print(" ```python") print(" api = PocketOption(ssid, demo=True)") @@ -265,11 +270,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,10 +286,10 @@ def show_api_improvements(): def show_keep_alive_features(): """Show specific keep-alive features""" - + print("๐Ÿ”„ 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") @@ -295,7 +302,7 @@ def show_keep_alive_features(): print("โœ… Graceful shutdown and cleanup") print("โœ… Complete SSID format support") print() - + print("๐Ÿ”ง TECHNICAL IMPLEMENTATION:") print("โ€ข AsyncWebSocketClient with persistent connections") print("โ€ข ConnectionKeepAlive manager for advanced scenarios") @@ -305,7 +312,7 @@ def show_keep_alive_features(): print("โ€ข Message batching and optimization") print("โ€ข Health monitoring with alerts") print() - + print("๐Ÿ“Š MONITORING CAPABILITIES:") print("โ€ข Connection uptime tracking") print("โ€ข Message send/receive counters") @@ -314,7 +321,7 @@ def show_keep_alive_features(): print("โ€ข Health check results") print("โ€ข Performance metrics collection") print() - + print("๐ŸŽ›๏ธ CONFIGURATION OPTIONS:") print("โ€ข persistent_connection: Enable advanced keep-alive") print("โ€ข auto_reconnect: Automatic reconnection on failure") @@ -326,18 +333,18 @@ def show_keep_alive_features(): async def main(): """Main demo function""" - + 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() diff --git a/examples/async_examples.py b/examples/async_examples.py index 0812109..e7d72c5 100644 --- a/examples/async_examples.py +++ b/examples/async_examples.py @@ -4,7 +4,6 @@ import asyncio import os -from datetime import datetime, timedelta from loguru import logger # Configure logging @@ -15,74 +14,70 @@ OrderDirection, PocketOptionError, ConnectionError, - OrderError + 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 + 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 + 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 + 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 + 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: @@ -99,189 +94,202 @@ async def basic_example(): 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) - + 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 - + 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 + duration=120, # 2 minutes ) - + orders.append(order_result) - logger.info(f"Placed {direction.value} order for {asset}: {order_result.order_id}") - + 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"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 - + 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 - + 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 + duration=60, ) - - logger.info(f"Strategy triggered: {direction.value} order placed: {order.order_id}") - + + 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": diff --git a/load_testing_tool.py b/load_testing_tool.py index 9dd8d76..461cc43 100644 --- a/load_testing_tool.py +++ b/load_testing_tool.py @@ -4,13 +4,11 @@ """ 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 +21,7 @@ @dataclass class LoadTestResult: """Result of a load test operation""" + operation_type: str start_time: datetime end_time: datetime @@ -35,6 +34,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 +46,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 +98,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...") - + try: await asyncio.gather(*client_tasks, return_exceptions=True) finally: @@ -115,24 +117,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!") - + # 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 Phase: {phase_name} ({clients} clients, {delay}s delay)" + ) + # Create tasks for this phase phase_tasks = [] for i in range(clients): @@ -141,16 +145,16 @@ 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") @@ -158,293 +162,329 @@ async def _run_stress_test(self, config: LoadTestConfig) -> Dict[str, Any]: 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} - )) - + 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") 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"โŒ 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}") - + 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"โŒ Stress client {client_id} operation {op_num} failed: {e}" + ) + except Exception as e: logger.error(f"โŒ 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"๐Ÿ“Š 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...") - + 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") - + 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 +493,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 +528,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") - + # Create load tester load_tester = LoadTester(ssid, is_demo=True) - + # Test configurations test_configs = [ LoadTestConfig( @@ -548,110 +617,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"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" 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}") - + # 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}") - + 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}") - + # Final summary logger.info("\n๐Ÿ 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 index be85ad6..aec549d 100644 --- a/migration_guide.py +++ b/migration_guide.py @@ -3,8 +3,6 @@ """ import asyncio -import time -from datetime import datetime from typing import Optional # Import the old API for comparison @@ -19,7 +17,7 @@ AsyncPocketOptionClient, OrderDirection, OrderStatus, - PocketOptionError + PocketOptionError, ) @@ -28,23 +26,20 @@ 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.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: @@ -55,18 +50,18 @@ async def connect(self) -> bool: 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: @@ -75,37 +70,37 @@ async def get_balance(self) -> Optional[float]: 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]]: + + 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 - + 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 + 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) """ @@ -113,65 +108,65 @@ async def check_win(self, order_id: str) -> tuple[Optional[float], str]: # 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 + 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 - }) - + 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() @@ -181,39 +176,35 @@ 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 + 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: @@ -225,50 +216,49 @@ 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) - + 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 + 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 + 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: @@ -277,11 +267,11 @@ def on_order_closed(order_result): async def side_by_side_comparison(): """Show side-by-side comparison of old vs new API""" - - print("\n" + "="*60) + + print("\n" + "=" * 60) print("MIGRATION COMPARISON") - print("="*60) - + print("=" * 60) + print("\n๐Ÿ“ OLD SYNCHRONOUS API:") print(""" from pocketoptionapi.stable_api import PocketOption @@ -305,7 +295,7 @@ async def side_by_side_comparison(): # Cleanup api.disconnect() """) - + print("\n๐Ÿš€ NEW ASYNC API:") print(""" from pocketoptionapi_async import AsyncPocketOptionClient, OrderDirection @@ -340,7 +330,7 @@ def on_order_closed(result): asyncio.run(main()) """) - + print("\nโœ… KEY IMPROVEMENTS:") print("- 100% async/await support") print("- Type safety with Pydantic models") @@ -354,11 +344,11 @@ def on_order_closed(result): def print_migration_checklist(): """Print migration checklist""" - - print("\n" + "="*60) + + print("\n" + "=" * 60) print("MIGRATION CHECKLIST") - print("="*60) - + print("=" * 60) + checklist = [ "โœ… Install new dependencies (pip install -r requirements.txt)", "โœ… Update imports to use pocketoptionapi_async", @@ -369,12 +359,12 @@ def print_migration_checklist(): "โœ… 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" + "โœ… 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") @@ -385,26 +375,26 @@ def print_migration_checklist(): async def main(): """Main migration demonstration""" - + print("๐Ÿ”„ POCKETOPTION API MIGRATION GUIDE") - print("="*50) - + 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") diff --git a/performance_tests.py b/performance_tests.py index 16f9873..9fae6ec 100644 --- a/performance_tests.py +++ b/performance_tests.py @@ -7,274 +7,292 @@ 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...") - + 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("-" * 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") + 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") else: report.append("โŒ Connection tests failed") except Exception as e: report.append(f"โŒ Connection test error: {e}") - + report.append("") - + # Test 2: Data Retrieval Performance report.append("๐Ÿ“Š DATA RETRIEVAL PERFORMANCE") report.append("-" * 35) @@ -283,32 +301,40 @@ async def generate_performance_report(self) -> str: 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("") - + # Test 3: Concurrent Operations report.append("โšก 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") + 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") else: report.append("โŒ Concurrent operations failed") except Exception as e: report.append(f"โŒ 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 +343,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_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..e6a060e 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 + # 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,75 +199,99 @@ 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}") - + # 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...") - + # 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") @@ -253,10 +303,10 @@ async def _start_persistent_connection(self, regions: Optional[List[str]] = None async def _start_keep_alive_tasks(self): """Start 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,11 +326,11 @@ 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 - + self._connection_stats["total_reconnects"] += 1 + try: success = await self._start_regular_connection() if success: @@ -295,67 +345,71 @@ async def _reconnection_monitor(self): async def disconnect(self) -> None: """Disconnect from PocketOption and cleanup all resources""" 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,23 +772,25 @@ 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) @@ -712,29 +798,35 @@ async def _wait_for_order_result(self, request_id: str, order: Order, timeout: f if self.enable_logging: 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""" @@ -992,20 +1124,21 @@ 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) + 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) + await self._emit_event("balance_updated", balance) except Exception as e: if self.enable_logging: logger.error(f"Failed to parse balance data: {e}") @@ -1019,51 +1152,59 @@ 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) + 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,44 +1213,49 @@ 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) + 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}") @@ -1117,22 +1263,22 @@ async def _handle_candles_stream(self, data: Dict[str, Any]) -> None: 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)...") - + for attempt in range(max_attempts): try: 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}") - + # Trigger reconnected event - await self._emit_event('reconnected', {}) + await self._emit_event("reconnected", {}) return True else: 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"โŒ 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/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..1cbe476 100644 --- a/pocketoptionapi_async/models.py +++ b/pocketoptionapi_async/models.py @@ -2,16 +2,18 @@ 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): CALL = "call" PUT = "put" + class OrderStatus(str, Enum): PENDING = "pending" ACTIVE = "active" @@ -20,35 +22,42 @@ class OrderStatus(str, Enum): WIN = "win" LOSE = "lose" + class ConnectionStatus(str, Enum): CONNECTED = "connected" DISCONNECTED = "disconnected" CONNECTING = "connecting" RECONNECTING = "reconnecting" + class Asset(BaseModel): """Asset information model""" + id: str name: str symbol: str is_active: bool = True payout: Optional[float] = None - + class Config: frozen = True + class Balance(BaseModel): """Account balance model""" + 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""" + timestamp: datetime open: float high: float @@ -57,44 +66,48 @@ class Candle(BaseModel): volume: Optional[float] = None asset: str timeframe: int # in seconds - - @validator('high') + + @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') + 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') + 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""" + asset: str amount: float direction: OrderDirection duration: int # in seconds request_id: Optional[str] = Field(default_factory=lambda: str(uuid.uuid4())) - - @validator('amount') + + @validator("amount") def amount_must_be_positive(cls, v): 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): 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_id: str asset: str amount: float @@ -106,28 +119,32 @@ 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_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""" + 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/setup.py b/setup.py index a7c727f..31a0f7e 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,9 @@ 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("#")] + requirements = [ + line.strip() for line in fh if line.strip() and not line.startswith("#") + ] setup( name="pocketoption-async-api", @@ -50,7 +52,7 @@ "jupyter>=1.0.0", "matplotlib>=3.5.0", "seaborn>=0.11.0", - ] + ], }, entry_points={ "console_scripts": [ diff --git a/test.py b/test.py index aaf6398..c9d0714 100644 --- a/test.py +++ b/test.py @@ -1,16 +1,13 @@ -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") +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() @@ -29,15 +26,13 @@ async def main(): # 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 - ) + 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: @@ -45,4 +40,4 @@ async def main(): except Exception as e: print(f"An error occurred: {e}") finally: - print("Closing connection...") \ No newline at end of file + print("Closing connection...") 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..20f1e64 100644 --- a/tests/enhanced_test.py +++ b/tests/enhanced_test.py @@ -10,118 +10,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") 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") - + # 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") - + except Exception as 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") - + 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") - + except Exception as 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 +133,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}") except Exception as e: logger.error(f"โŒ Recovery failed: {e}") - + async def test_concurrent_performance(self): """Test concurrent operations 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:") + 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) + 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 +222,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(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,92 +247,94 @@ 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']: + + 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") 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}...") @@ -338,7 +343,7 @@ async def run_all_tests(self): await asyncio.sleep(1) # Brief pause between tests except Exception as e: logger.error(f"โŒ {test_name} failed: {e}") - + # Generate final report await self.generate_performance_report() @@ -356,17 +361,19 @@ async def main(): 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 +383,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..6ca52bd 100644 --- a/tests/integration_tests.py +++ b/tests/integration_tests.py @@ -7,31 +7,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("=" * 60) - + # Test phases test_phases = [ ("Basic Connectivity", self.test_basic_connectivity), @@ -43,67 +42,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 +126,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 +156,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 +243,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 +315,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,445 +413,562 @@ 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.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"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: + 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}") raise @@ -818,11 +976,11 @@ async def run_integration_tests(ssid: str = None): 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..4cfc3ce 100644 --- a/tests/test_balance_fix.py +++ b/tests/test_balance_fix.py @@ -4,89 +4,92 @@ """ 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) - + + 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.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="{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..002938e 100644 --- a/tests/test_candles_fix.py +++ b/tests/test_candles_fix.py @@ -4,53 +4,56 @@ 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("=" * 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!") - + # Display first few candles print("\n๐Ÿ“ˆ Sample 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") - + # Test 2: Get candles as DataFrame print("\n๐Ÿ“Š Testing DataFrame conversion...") try: @@ -63,15 +66,11 @@ async def test_candles_retrieval(): print("โŒ Empty DataFrame received") except Exception as 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) @@ -81,26 +80,31 @@ async def test_candles_retrieval(): print(f"โŒ {tf_name}: No data") except Exception as 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") except Exception as e: print(f"โŒ {test_asset}: Error - {e}") - + except Exception as e: print(f"โŒ Test failed with error: {e}") import traceback + traceback.print_exc() - + finally: try: await client.disconnect() @@ -108,55 +112,57 @@ async def test_candles_retrieval(): 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("=" * 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("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..b94b78a 100644 --- a/tests/test_complete_order_tracking.py +++ b/tests/test_complete_order_tracking.py @@ -6,7 +6,6 @@ import asyncio import os -import time from datetime import datetime, timedelta from loguru import logger @@ -15,61 +14,67 @@ 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("Example: set POCKET_OPTION_SSID='your_session_id_here'") return - + 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") return - + 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})") else: 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" + 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) - + + 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("\n๐Ÿ“ˆ Placing 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" Status: {order_result.status}") print(f" Asset: {order_result.asset}") @@ -77,89 +82,108 @@ def on_order_closed(order_result): 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!") 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("\n๐ŸŽฏ TRADE 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") 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("\nโŒ Could 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}") import traceback + traceback.print_exc() - + finally: # Disconnect - print(f"\n๐Ÿ”Œ Disconnecting...") + print("\n๐Ÿ”Œ Disconnecting...") await client.disconnect() print("โœ… Test completed") @@ -170,7 +194,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..8250a12 100644 --- a/tests/test_complete_ssid.py +++ b/tests/test_complete_ssid.py @@ -5,31 +5,30 @@ 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("=" * 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(f"๐Ÿ“ค Formatted message: {formatted_message[:50]}...") print(f"๐Ÿ” Session extracted: {getattr(client, 'session_id', 'N/A')[:20]}...") @@ -37,7 +36,7 @@ async def test_complete_ssid_format(): print(f"๐Ÿท๏ธ Platform: {client.platform}") 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...") try: @@ -49,115 +48,118 @@ async def test_complete_ssid_format(): print("โ„น๏ธ Connection failed (expected with test SSID)") except Exception as e: print(f"โ„น๏ธ Connection error (expected): {str(e)[:100]}...") - + await client.disconnect() - + except Exception as 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(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("\n" + "=" * 50) 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(" 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...") await client.connect() - + if client.is_connected: print("โœ… Successfully connected!") - + # Test basic functionality try: balance = await client.get_balance() 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}") - + else: print("โŒ Connection failed") - + await client.disconnect() print("๐Ÿ”Œ Disconnected") - + except Exception as e: print(f"โŒ Connection error: {e}") async def main(): """Main test function""" - + 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..1e8e9d0 100644 --- a/tests/test_demo_live_connection.py +++ b/tests/test_demo_live_connection.py @@ -6,61 +6,62 @@ 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)") 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}") - + # Test 2: Live mode connection (should try non-demo regions) print("\n2๏ธโƒฃ Test: 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("\n" + "=" * 50) print("โœ… Demo/Live Connection Test Complete!") print("\nKey improvements:") @@ -69,5 +70,6 @@ async def test_demo_live_connection(): 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..de291f5 100644 --- a/tests/test_demo_live_fix.py +++ b/tests/test_demo_live_fix.py @@ -7,98 +7,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("=" * 50) - + # Test 1: Demo mode with demo SSID (should work) print("\n1๏ธโƒฃ Test: 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']}") - + # Test 2: Live mode with demo SSID (should override to live) print("\n2๏ธโƒฃ Test: 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']}") - + # Test 3: Raw session ID with demo mode print("\n3๏ธโƒฃ Test: 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']}") - + # 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) + 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']}") - + # 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!") - + # 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.") - + 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..2d25ab2 100644 --- a/tests/test_fixed_connection.py +++ b/tests/test_fixed_connection.py @@ -10,47 +10,53 @@ # 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(f"๐Ÿ” Session ID: {client.session_id}") print(f"๐Ÿ‘ค UID: {client.uid}") print(f"๐ŸŽฏ Demo mode: {client.is_demo}") print(f"๐Ÿท๏ธ Platform: {client.platform}") print() - + # Test connection print("๐Ÿ”Œ Testing connection with improved handshake...") try: success = await client.connect() - + if success: 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: @@ -61,45 +67,48 @@ async def test_connection_fix(): 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!") - + else: 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]}...") 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") - + finally: await client.disconnect() print("๐Ÿ›‘ Disconnected") - + except Exception as 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("=" * 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") @@ -107,7 +116,7 @@ async def test_old_vs_new_comparison(): 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") @@ -115,23 +124,26 @@ async def test_old_vs_new_comparison(): 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() - + # 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( + "๐Ÿ“ 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") @@ -141,5 +153,6 @@ async def main(): 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..5126263 100644 --- a/tests/test_new_api.py +++ b/tests/test_new_api.py @@ -4,47 +4,47 @@ 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("=" * 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 - ) + 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...") try: await client.connect() print("โœ… Connected successfully") - + # Test 3: Get balance print("\n3๏ธโƒฃ Testing balance retrieval...") try: @@ -52,19 +52,17 @@ async def test_basic_functionality(): print(f"โœ… Balance: ${balance.balance:.2f} ({balance.currency})") except Exception as e: print(f"โ„น๏ธ Balance test: {e}") - + # Test 4: Get candles print("\n4๏ธโƒฃ Testing 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") except Exception as 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}") except Exception as e: print(f"โ„น๏ธ Order test: {e}") - + except ConnectionError as e: print(f"โ„น๏ธ Connection test (expected with mock session): {e}") - + finally: # Test 6: Disconnection print("\n6๏ธโƒฃ Testing disconnection...") await client.disconnect() print("โœ… Disconnected successfully") - + except Exception as e: print(f"โŒ Unexpected error: {e}") - + print("\n๐ŸŽฏ API 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( + " 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,57 +117,55 @@ def test_api_structure(): Order, OrderResult, ASSETS, - REGIONS + REGIONS, ) + print("โœ… All imports successful") except ImportError as e: print(f"โŒ Import error: {e}") return - + # Test enums assert OrderDirection.CALL == "call" assert OrderDirection.PUT == "put" print("โœ… Enums working correctly") - + # Test constants assert "EURUSD_otc" in ASSETS assert len(REGIONS.get_all()) > 0 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") - + # 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") except ValueError: print("โœ… Model validation correctly catches errors") - + except Exception as 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") @@ -179,38 +177,38 @@ async def test_context_manager(): 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) + + client.add_event_callback("test_event", test_callback) print("โœ… Event callback registered") - + # Test callback removal - client.remove_event_callback('test_event', test_callback) + 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("=" * 40) - + features = [ "โœ… 100% Async/Await Support", "โœ… Type Safety with Pydantic Models", "โœ… Professional Error Handling", - "โœ… Automatic Connection Management", + "โœ… Automatic Connection Management", "โœ… Event-Driven Architecture", "โœ… pandas DataFrame Integration", "โœ… Built-in Rate Limiting", @@ -218,19 +216,19 @@ def print_api_features(): "โœ… Comprehensive Testing", "โœ… Rich Logging with loguru", "โœ… WebSocket Auto-Reconnection", - "โœ… Modern Python Practices" + "โœ… 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..77370e1 100644 --- a/tests/test_order_fix.py +++ b/tests/test_order_fix.py @@ -7,23 +7,24 @@ 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...") await client.connect() - + if client.is_connected: logger.success("โœ… Connected successfully!") - + # Wait for authentication and balance await asyncio.sleep(3) - + try: balance = await client.get_balance() if balance: @@ -32,7 +33,7 @@ async def test_order_placement(): logger.warning("โš ๏ธ No balance data received") except Exception as 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...") try: @@ -40,33 +41,36 @@ async def test_order_placement(): 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}") # 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!") 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)") - + except Exception as e: logger.error(f"โŒ Connection error: {e}") - + finally: await client.disconnect() logger.info("๐Ÿ”Œ Disconnected") + if __name__ == "__main__": logger.info("๐Ÿงช Testing Order Placement Fix") logger.info("=" * 50) diff --git a/tests/test_order_logging_fixes.py b/tests/test_order_logging_fixes.py index a646417..05d0698 100644 --- a/tests/test_order_logging_fixes.py +++ b/tests/test_order_logging_fixes.py @@ -5,7 +5,6 @@ import asyncio import os -from datetime import datetime from loguru import logger from pocketoptionapi_async import AsyncPocketOptionClient, OrderDirection @@ -13,120 +12,118 @@ 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") return - + print("๐Ÿงช Testing Order Tracking and Logging Fixes...") - + # Test 1: Client with logging enabled (default) print("\n1๏ธโƒฃ Test: 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") return - + 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})") - + # Place a test order print("\n๐ŸŽฏ Placing 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" 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") else: 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}") - + 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") 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") return - + 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})") - + # Place a test order (should work silently) print("\n๐ŸŽฏ Placing 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" 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)") else: print("โŒ Order NOT found in tracking") - + finally: await client_no_logs.disconnect() - + print("\nโœ… Tests completed!") @@ -136,7 +133,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..21c320c 100644 --- a/tests/test_order_placement_fix.py +++ b/tests/test_order_placement_fix.py @@ -5,7 +5,6 @@ import asyncio import os -from datetime import datetime from loguru import logger # Configure logger @@ -13,7 +12,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 +20,91 @@ 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.info("Set POCKET_OPTION_SSID environment variable for real testing") - + 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'): + 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...") - + await client.connect() - + if client.is_connected: 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!") 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.success("๐ŸŽ‰ Order placement fix test completed!") - + except Exception as 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..4e53c38 100644 --- a/tests/test_order_tracking_complete.py +++ b/tests/test_order_tracking_complete.py @@ -10,7 +10,6 @@ import asyncio import os -import time from datetime import datetime, timedelta from loguru import logger @@ -19,85 +18,85 @@ 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("Example: set POCKET_OPTION_SSID='your_session_id_here'") return - + 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") return - + 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})") else: 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" 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)") else: 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: @@ -107,160 +106,189 @@ async def test_complete_order_lifecycle(): else: 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("\n๐ŸŽฏ TRADE 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") 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") tests_passed += 1 else: print("โŒ Order Placement (No Duplication): FAIL") - + if immediate_result: print("โœ… Order Tracking: PASS") tests_passed += 1 else: print("โŒ Order Tracking: FAIL") - + if completed_orders: print("โœ… Event-Based Completion: PASS") tests_passed += 1 else: print("โŒ Event-Based Completion: FAIL") - + if final_result_poll and final_result_poll.profit is not None: print("โœ… Polling-Based Completion: PASS") tests_passed += 1 else: print("โŒ Polling-Based Completion: FAIL") - + # Additional checks if len(final_active_orders) < len(active_orders_after): print("โœ… Order Movement (Active -> Completed): PASS") tests_passed += 1 else: print("โŒ Order Movement (Active -> Completed): FAIL") - + if balance: print("โœ… Balance Retrieval: PASS") tests_passed += 1 else: print("โŒ Balance Retrieval: FAIL") - + print(f"\n๐ŸŽฏ OVERALL 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") else: print("โŒ Major issues with order tracking system") - + except Exception as e: print(f"โŒ Error: {e}") import traceback + traceback.print_exc() - + finally: # Disconnect - print(f"\n๐Ÿ”Œ Disconnecting...") + print("\n๐Ÿ”Œ Disconnecting...") await client.disconnect() print("โœ… Test completed") @@ -271,7 +299,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..90ab49c 100644 --- a/tests/test_order_tracking_fix.py +++ b/tests/test_order_tracking_fix.py @@ -14,60 +14,57 @@ 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") return - + 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") return - + 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})") else: print("โš ๏ธ No balance received") - + # Place a test order print("\n๐ŸŽฏ Placing 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" 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(f" Order ID: {immediate_result.order_id}") @@ -77,37 +74,45 @@ async def test_order_tracking(): else: 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") 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: @@ -116,12 +121,13 @@ async def test_order_tracking(): print(f"๐Ÿ’ฐ Profit/Loss: ${final_result.profit:.2f}") else: print("๐Ÿ’ฐ Profit/Loss: Not yet determined") - + except Exception as e: print(f"โŒ Error: {e}") import traceback + traceback.print_exc() - + finally: # Disconnect print("\n๐Ÿ”Œ Disconnecting...") @@ -135,7 +141,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..1eb7608 100644 --- a/tests/test_persistent_connection.py +++ b/tests/test_persistent_connection.py @@ -6,8 +6,6 @@ import asyncio import os -import time -from datetime import datetime, timedelta from loguru import logger from pocketoptionapi_async import AsyncPocketOptionClient @@ -15,7 +13,7 @@ async def test_persistent_connection(): """Test persistent connection with automatic keep-alive""" - + print("๐Ÿงช Testing Persistent Connection with Keep-Alive") print("=" * 60) print("This test demonstrates the enhanced connection management") @@ -27,192 +25,206 @@ async def test_persistent_connection(): 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") - + # 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)") - + await client_regular.disconnect() print("โœ… Regular connection test completed") - + except Exception as e: 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("-" * 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}") - + 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") - + # 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)") - + await client_persistent.disconnect() print("โœ… Persistent connection test completed") - + except Exception as e: 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") - + # 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}") except Exception as e: print(f" โš ๏ธ Balance check failed: {e}") - + await resilience_client.disconnect() print("โœ… Resilience test completed") else: print("โŒ Resilient connection failed") - + except Exception as e: print(f"โŒ Resilience test error: {e}") else: print("โ„น๏ธ Skipping resilience test (requires real SSID)") - + print() print("๐ŸŽ‰ All persistent connection tests completed!") print() @@ -233,10 +245,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 +258,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") @@ -261,7 +273,7 @@ async def test_comparison_with_old_api(): if __name__ == "__main__": 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..c012be1 100644 --- a/tests/test_ssid_formats.py +++ b/tests/test_ssid_formats.py @@ -6,109 +6,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("=" * 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(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}") - + formatted_message = client1._format_session_message() print(f"โœ… Formatted message: {formatted_message}") print() - + # Test 2: Raw session ID raw_session = "n1p5ah5u8t9438rbunpgrq0hlq" - + print("2๏ธโƒฃ 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}") - + formatted_message2 = client2._format_session_message() print(f"โœ… Formatted message: {formatted_message2}") print() - + # Test 3: Verify both produce same result print("3๏ธโƒฃ 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!") else: print("โŒ Authentication data mismatch!") - + print() - + # Test 4: Test connection with real SSID format (mock) print("4๏ธโƒฃ Testing Connection with Complete SSID") - + try: # This will fail with test data, but should show proper SSID handling await client1.connect() 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('โœ… 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("\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 +118,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("=" * 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}") From ce0372178f645247debdcb05a06b37acf389fe50 Mon Sep 17 00:00:00 2001 From: sixtysxx Date: Thu, 19 Jun 2025 05:53:27 -0600 Subject: [PATCH 6/8] testing SSID retrieval --- connection_keep_alive.py | 6 +++--- examples/async_examples.py | 1 + pocketoptionapi_async/utils.py | 35 +++++++++++++++++++++++++++++++++- 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/connection_keep_alive.py b/connection_keep_alive.py index f87e410..d7e0493 100644 --- a/connection_keep_alive.py +++ b/connection_keep_alive.py @@ -188,7 +188,7 @@ async def _establish_connection(self) -> bool: if self.websocket: try: await self.websocket.close() - except: + except Exception: pass self.websocket = None @@ -360,7 +360,7 @@ async def _reconnection_monitor(self): if self.websocket: try: await self.websocket.close() - except: + except Exception: pass self.websocket = None @@ -468,7 +468,7 @@ def _extract_region_from_url(self, url: str) -> str: return "DEMO" else: return "UNKNOWN" - except: + except Exception: return "UNKNOWN" def get_connection_stats(self) -> Dict[str, Any]: diff --git a/examples/async_examples.py b/examples/async_examples.py index e7d72c5..d9b7402 100644 --- a/examples/async_examples.py +++ b/examples/async_examples.py @@ -16,6 +16,7 @@ ConnectionError, OrderError, ) +from pocketoptionapi_async.utils import fetch_ssid async def basic_example(): diff --git a/pocketoptionapi_async/utils.py b/pocketoptionapi_async/utils.py index 86f93f7..244e21b 100644 --- a/pocketoptionapi_async/utils.py +++ b/pocketoptionapi_async/utils.py @@ -11,6 +11,29 @@ from .models import Candle, OrderResult +async def fetch_ssid(): + urls = [ + "wss://ws.pocketoption.com/socket.io/?EIO=3&transport=websocket", + "wss://ws-demo.pocketoption.com/socket.io/?EIO=3&transport=websocket", + ] + + for uri in urls: + try: + async with websockets.connect(uri) as websocket: + while True: + message = await websocket.recv() + if message.startswith('42["auth"'): + return message + elif message == '2': + await websocket.send('3') + elif message.startswith('0'): + sid = message.split('"sid":"')[1].split('"')[0] + await websocket.send(f'2/{sid}') + except Exception as e: + print(f"Failed to connect to {uri}: {e}") + continue + + raise Exception("Unable to fetch SSID from any endpoint") def format_session_id( session_id: str, @@ -46,7 +69,17 @@ def format_session_id( return f'42["auth",{json.dumps(auth_data)}]' - +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 + ) def calculate_payout_percentage( entry_price: float, exit_price: float, direction: str, payout_rate: float = 0.8 ) -> float: From c6f5768246d39a0ef728d46de084b96e54708bba Mon Sep 17 00:00:00 2001 From: sixtysxx Date: Sun, 22 Jun 2025 20:44:44 -0600 Subject: [PATCH 7/8] provide env example and remove empty files --- .env.example | 1 + test.py | 5 +++-- test_candles_fix.py | 0 test_order_logging_fixes.py | 0 4 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 .env.example delete mode 100644 test_candles_fix.py delete mode 100644 test_order_logging_fixes.py 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/test.py b/test.py index c9d0714..b87f97e 100644 --- a/test.py +++ b/test.py @@ -1,9 +1,10 @@ import dotenv from pocketoptionapi_async import AsyncPocketOptionClient - +import os dotenv.load_dotenv() -ssid = r'42["auth",{"session":"t04ppgptp3404h0lajp4bo7smh","isDemo":1,"uid":101884312,"platform":2,"isFastHistory":true}]' # os.getenv("SSID") +#ssid = r'42["auth",{"session":"t04ppgptp3404h0lajp4bo7smh","isDemo":1,"uid":101884312,"platform":2,"isFastHistory":true}]' +ssid = os.getenv("SSID") print(ssid) api = AsyncPocketOptionClient(ssid=ssid, is_demo=True) 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 From c31d11b499021c7ba4bd2b09cbe6717782d6e5c6 Mon Sep 17 00:00:00 2001 From: sixtysxx Date: Sun, 22 Jun 2025 21:35:56 -0600 Subject: [PATCH 8/8] Added SSID Gathering, fixed TimeFrame import issue, and organzied a little bit --- .gitignore | 6 +- README.md | 475 +----------------- SSID/driver.py | 116 +++++ SSID/get_ssid.py | 141 ++++++ TODO/todo.md | 6 + advanced_testing_suite.py | 59 +-- comprehensive_demo.py | 253 +++++----- connection_keep_alive.py | 95 ++-- connection_monitor.py | 61 ++- demo_enhanced_api.py | 181 +++---- docs/todo.md | 6 - examples/async_examples.py | 304 ----------- load_testing_tool.py | 43 +- migration_guide.py | 403 --------------- performance_tests.py | 43 +- pocketoptionapi_async/client.py | 60 +-- .../connection_keep_alive.py.bak | 219 -------- pocketoptionapi_async/models.py | 107 +++- pocketoptionapi_async/utils.py | 35 +- run_api.ps1 | 231 --------- setup.py | 70 --- test.py | 44 -- tests/enhanced_test.py | 55 +- tests/integration_tests.py | 19 +- tests/test_balance_fix.py | 25 +- tests/test_candles_fix.py | 36 +- tests/test_complete_order_tracking.py | 39 +- tests/test_complete_ssid.py | 41 +- tests/test_demo_live_connection.py | 27 +- tests/test_demo_live_fix.py | 23 +- tests/test_fixed_connection.py | 55 +- tests/test_new_api.py | 92 ++-- tests/test_order_fix.py | 29 +- tests/test_order_logging_fixes.py | 41 +- tests/test_order_placement_fix.py | 29 +- tests/test_order_tracking_complete.py | 75 ++- tests/test_order_tracking_fix.py | 37 +- tests/test_persistent_connection.py | 71 ++- tests/test_ssid_formats.py | 57 ++- 39 files changed, 1156 insertions(+), 2553 deletions(-) create mode 100644 SSID/driver.py create mode 100644 SSID/get_ssid.py create mode 100644 TODO/todo.md delete mode 100644 docs/todo.md delete mode 100644 examples/async_examples.py delete mode 100644 migration_guide.py delete mode 100644 pocketoptionapi_async/connection_keep_alive.py.bak delete mode 100644 run_api.ps1 delete mode 100644 setup.py delete mode 100644 test.py 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/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 83f59cd..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 @@ -27,7 +26,7 @@ def __init__(self, ssid: str): 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), @@ -43,7 +42,7 @@ async def run_all_tests(self) -> Dict[str, Any]: ] 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() @@ -55,7 +54,9 @@ async def run_all_tests(self) -> Dict[str, Any]: "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] = { @@ -63,13 +64,13 @@ async def run_all_tests(self) -> Dict[str, Any]: "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) @@ -104,7 +105,7 @@ async def test_connection_stress(self) -> bool: 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) @@ -143,7 +144,7 @@ async def ping_task(): 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) @@ -186,7 +187,7 @@ async def test_data_consistency(self) -> bool: 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) @@ -197,14 +198,14 @@ async def test_error_handling(self) -> bool: 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() @@ -212,7 +213,7 @@ async def test_error_handling(self) -> bool: 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 @@ -222,7 +223,7 @@ async def test_error_handling(self) -> bool: 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) @@ -257,7 +258,7 @@ async def test_performance_benchmarks(self) -> bool: "avg_message_time": message_time, } - logger.info("๐Ÿ“ˆ 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") @@ -279,7 +280,7 @@ async def test_performance_benchmarks(self) -> bool: 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 @@ -323,7 +324,7 @@ async def test_memory_usage(self) -> bool: 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 @@ -384,7 +385,7 @@ def handler(data): 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) @@ -437,7 +438,7 @@ async def test_long_running_session(self) -> bool: 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) @@ -476,7 +477,7 @@ async def get_asset_candles(asset): 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) @@ -569,11 +570,13 @@ async def run_advanced_tests(ssid: str = None): 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: @@ -581,20 +584,20 @@ async def run_advanced_tests(ssid: str = None): # Print summary logger.info("\n" + "=" * 60) - logger.info("๐Ÿ“‹ TEST SUMMARY") + logger.info("Demonstration: TEST SUMMARY") logger.info("=" * 60) 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") + logger.info("\nStatistics: PERFORMANCE METRICS") logger.info("-" * 30) for metric, value in report["performance_metrics"].items(): logger.info(f"{metric}: {value:.3f}s") @@ -604,12 +607,12 @@ async def run_advanced_tests(ssid: str = None): 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 diff --git a/comprehensive_demo.py b/comprehensive_demo.py index 9f5cc87..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 @@ -18,13 +17,13 @@ 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}]' @@ -35,14 +34,14 @@ async def demo_ssid_format_support(): 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("โ€ข 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() @@ -51,25 +50,25 @@ async def demo_ssid_format_support(): 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(f"Note: Demo connection attempt: {e}") - logger.info("โœ… Complete SSID format is fully supported!") + 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( @@ -81,7 +80,7 @@ async def demo_persistent_connection(): 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() @@ -92,7 +91,7 @@ async def demo_persistent_connection(): 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: @@ -101,13 +100,13 @@ async def demo_persistent_connection(): 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) @@ -118,23 +117,25 @@ async def demo_persistent_connection(): async def on_connected(data): events_count["connected"] += 1 logger.success( - f"๐ŸŽ‰ Keep-alive connected to: {data.get('region', 'Unknown')}" + 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]}...") + 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) @@ -143,13 +144,13 @@ async def on_message(data): 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") + 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"Statistics: Stats: Connected={stats['is_connected']}, " f"Messages={stats['total_messages_sent']}, " f"Uptime={stats.get('uptime', 'N/A')}" ) @@ -157,12 +158,14 @@ async def on_message(data): 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(f"Note: Demo keep-alive: {e}") - logger.info("\nโœ… Persistent connection features demonstrated!") + 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") @@ -173,12 +176,12 @@ 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) @@ -188,7 +191,7 @@ async def demo_advanced_monitoring(): 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 @@ -199,10 +202,10 @@ async def on_stats_update(stats): 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) @@ -210,13 +213,13 @@ async def on_stats_update(stats): 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"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( @@ -228,19 +231,21 @@ async def on_stats_update(stats): logger.info(f"โ€ข Uptime: {report['real_time_stats']['uptime_str']}") if report["recommendations"]: - logger.info("๐Ÿ’ก 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(f"Note: Demo monitoring: {e}") - logger.info("\nโœ… Advanced monitoring features demonstrated!") + logger.info("\nSuccess: Advanced monitoring features demonstrated!") logger.info("โ€ข Real-time connection health monitoring") logger.info("โ€ข Performance metrics collection") logger.info("โ€ข Automatic alert generation") @@ -251,12 +256,12 @@ 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) @@ -271,7 +276,7 @@ async def demo_load_testing(): ) logger.info( - f"๐Ÿ“‹ Configuration: {config.concurrent_clients} clients, " + f"Demonstration: Configuration: {config.concurrent_clients} clients, " f"{config.operations_per_client} operations each" ) @@ -279,7 +284,7 @@ async def demo_load_testing(): # Show results summary = report["test_summary"] - logger.info("โœ… 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%}") @@ -290,7 +295,7 @@ async def demo_load_testing(): # Show operation analysis if report["operation_analysis"]: - logger.info("\n๐Ÿ“Š Operation Analysis:") + logger.info("\nStatistics: Operation Analysis:") for op_type, stats in list(report["operation_analysis"].items())[ :2 ]: # Show first 2 @@ -301,14 +306,14 @@ async def demo_load_testing(): # 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(f"Note: Demo load testing: {e}") - logger.info("\nโœ… Load testing features demonstrated!") + logger.info("\nSuccess: Load testing features demonstrated!") logger.info("โ€ข Concurrent client simulation") logger.info("โ€ข Performance benchmarking") logger.info("โ€ข Stress testing capabilities") @@ -318,63 +323,67 @@ 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...") + 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__}" + 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}" + 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) @@ -384,22 +393,26 @@ async def demo_error_handling(): async def on_reconnected(data): reconnections.append(data) - logger.success(f"๐Ÿ”„ Reconnection #{data.get('attempt', '?')} successful!") + 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(f"Note: Demo reconnection: {e}") - logger.info("\nโœ… Error handling features demonstrated!") + 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") @@ -409,32 +422,32 @@ 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: @@ -451,7 +464,7 @@ async def demo_data_operations(): 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: @@ -466,7 +479,7 @@ async def demo_data_operations(): 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: @@ -482,18 +495,18 @@ async def get_asset_data(asset): 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(f"Note: Demo data operations: {e}") - logger.info("\nโœ… Enhanced data operations demonstrated!") + logger.info("\nSuccess: Enhanced data operations demonstrated!") logger.info("โ€ข Comprehensive balance information") logger.info("โ€ข Multi-asset candle retrieval") logger.info("โ€ข Pandas DataFrame integration") @@ -503,18 +516,18 @@ 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 @@ -546,10 +559,10 @@ async def demo_performance_optimizations(): } 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() @@ -574,10 +587,10 @@ async def demo_performance_optimizations(): 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() @@ -611,17 +624,17 @@ async def operation_batch(): 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") @@ -631,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)") @@ -647,7 +660,7 @@ async def demo_migration_compatibility(): 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)") @@ -656,24 +669,24 @@ async def demo_migration_compatibility(): 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): @@ -682,10 +695,10 @@ async def run_comprehensive_demo(ssid: str = None): 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" + "Caution: Using demo SSID - some features will have limited functionality" ) - logger.info("๐ŸŽ‰ PocketOption Async API - Comprehensive Feature Demo") + 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!") @@ -713,7 +726,7 @@ async def run_comprehensive_demo(ssid: str = None): 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): @@ -723,24 +736,24 @@ async def run_comprehensive_demo(ssid: str = None): # 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") @@ -748,13 +761,13 @@ async def run_comprehensive_demo(ssid: str = None): 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("") - logger.success("โœจ Demo completed successfully! The API is enhanced and ready! โœจ") + logger.success("Completed successfully! The API is enhanced and ready!") if __name__ == "__main__": diff --git a/connection_keep_alive.py b/connection_keep_alive.py index d7e0493..cdb4193 100644 --- a/connection_keep_alive.py +++ b/connection_keep_alive.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 """ Enhanced Keep-Alive Connection Manager for PocketOption Async API """ @@ -70,7 +69,7 @@ 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 @@ -78,20 +77,20 @@ async def start_persistent_connection(self) -> bool: # Start all background tasks await self._start_background_tasks() logger.success( - "โœ… Persistent connection established with keep-alive active" + "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 @@ -116,7 +115,7 @@ async def stop_persistent_connection(self): self.websocket = None self.is_connected = False - logger.info("โœ… Persistent connection stopped") + logger.info("Success: Persistent connection stopped") async def _establish_connection(self) -> bool: """ @@ -127,7 +126,7 @@ async def _establish_connection(self) -> bool: try: logger.info( - f"๐Ÿ”Œ Attempting connection to {url} (attempt {attempt + 1})" + f"Connecting: Attempting connection to {url} (attempt {attempt + 1})" ) # SSL context (like old API) @@ -172,13 +171,13 @@ async def _establish_connection(self) -> bool: # Send initial handshake (like old API) await self._send_handshake() - logger.success(f"โœ… Connected to {region} region successfully") + 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( @@ -225,7 +224,7 @@ async def _send_handshake(self): 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()) @@ -239,14 +238,14 @@ async def _start_background_tasks(self): # 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: @@ -256,16 +255,16 @@ async def _ping_loop(self): self.connection_stats["last_ping_time"] = datetime.now() self.connection_stats["total_messages_sent"] += 1 - logger.debug("๐Ÿ“ Ping sent") + 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 @@ -273,7 +272,7 @@ 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: @@ -288,30 +287,30 @@ async def _message_loop(self): 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 @@ -323,37 +322,39 @@ async def _health_monitor_loop(self): seconds=60 ): # No response for 60 seconds logger.warning( - "๐Ÿฅ Health check: No ping response, connection may be dead" + "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 if self.current_reconnect_attempts <= self.max_reconnect_attempts: logger.info( - f"๐Ÿ”„ Reconnection attempt {self.current_reconnect_attempts}/{self.max_reconnect_attempts}" + f"Persistent: Reconnection attempt {self.current_reconnect_attempts}/{self.max_reconnect_attempts}" ) # Clean up current connection @@ -368,7 +369,7 @@ async def _reconnection_monitor(self): success = await self._establish_connection() if success: - logger.success("โœ… Reconnection successful!") + logger.success("Success: Reconnection successful!") await self._emit_event( "reconnected", { @@ -380,12 +381,12 @@ async def _reconnection_monitor(self): ) else: logger.error( - f"โŒ Reconnection attempt {self.current_reconnect_attempts} failed" + 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" + f"Error: Max reconnection attempts ({self.max_reconnect_attempts}) reached" ) await self._emit_event( "max_reconnects_reached", @@ -394,7 +395,7 @@ async def _reconnection_monitor(self): 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)""" @@ -403,18 +404,18 @@ async def _process_message(self, message): if isinstance(message, bytes): message = message.decode("utf-8") - logger.debug(f"๐Ÿ“จ Received: {message[:100]}...") + 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") + logger.debug("Ping: Pong sent") return # Handle authentication success (like old API) if "successauth" in message: - logger.success("โœ… Authentication successful") + logger.success("Success: Authentication successful") await self._emit_event("authenticated", {}) return @@ -422,7 +423,7 @@ async def _process_message(self, 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""" @@ -430,13 +431,13 @@ async def send_message(self, message: str) -> bool: 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]}...") + 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 @@ -456,7 +457,7 @@ 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""" @@ -501,13 +502,13 @@ async def demo_keep_alive(): # 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]}...") + logger.info(f"Message: Message: {data['message'][:50]}...") keep_alive.add_event_handler("connected", on_connected) keep_alive.add_event_handler("reconnected", on_reconnected) @@ -519,7 +520,7 @@ async def on_message(data): if success: logger.info( - "๐Ÿš€ Keep-alive connection started, will maintain connection automatically..." + "Starting: Keep-alive connection started, will maintain connection automatically..." ) # Let it run for a while to demonstrate keep-alive @@ -530,7 +531,7 @@ async def on_message(data): if i % 10 == 0: stats = keep_alive.get_connection_stats() logger.info( - f"๐Ÿ“Š Stats: Connected={stats['is_connected']}, " + 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']}" @@ -541,7 +542,7 @@ async def on_message(data): 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 @@ -549,5 +550,5 @@ async def on_message(data): 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 f147c43..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 @@ -78,7 +77,7 @@ def __init__(self, ssid: str, is_demo: bool = True): 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 @@ -110,23 +109,23 @@ async def start_monitoring(self, persistent_connection: bool = True) -> bool: self.monitor_task = asyncio.create_task(self._monitoring_loop()) logger.success( - f"โœ… Monitoring started (connection time: {connection_time:.3f}s)" + 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 @@ -140,7 +139,7 @@ async def stop_monitoring(self): 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""" @@ -160,7 +159,7 @@ def _setup_event_handlers(self): async def _monitoring_loop(self): """Main monitoring loop""" - logger.info("๐Ÿ”„ Starting monitoring loop...") + logger.info("Persistent: Starting monitoring loop...") while self.is_monitoring: try: @@ -181,7 +180,7 @@ async def _monitoring_loop(self): 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""" @@ -225,7 +224,7 @@ async def _collect_performance_snapshot(self): 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""" @@ -289,7 +288,7 @@ async def _emit_monitoring_events(self): 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""" @@ -373,29 +372,29 @@ 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") + logger.info("Connection established") async def _on_disconnected(self, data): self.total_messages += 1 self.message_stats["disconnected"] += 1 - logger.warning("๐Ÿ”Œ Connection lost") + logger.warning("Connection lost") async def _on_reconnected(self, data): self.total_messages += 1 self.message_stats["reconnected"] += 1 - logger.info("๐Ÿ”„ Connection restored") + logger.info("Connection restored") async def _on_auth_error(self, data): self.total_errors += 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 @@ -619,7 +618,7 @@ def export_metrics_csv(self, filename: str = None) -> str: 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") @@ -673,20 +672,20 @@ async def _display_loop(self): 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%}") @@ -695,7 +694,7 @@ async def _display_loop(self): # Display performance if "avg_response_time" in stats: - print("โšก Performance:") + 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") @@ -703,14 +702,14 @@ async def _display_loop(self): # Display memory if available if "memory_usage_mb" in stats: - print("๐Ÿ’พ Resources:") + 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:") + print("Message: Message Types:") for msg_type, count in stats["message_types"].items(): print(f" {msg_type}: {count}") print() @@ -729,16 +728,16 @@ async def run_monitoring_demo(ssid: str = None): 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.warning("Caution: Using demo SSID for monitoring") - logger.info("๐Ÿ” Starting Advanced Connection Monitor Demo") + 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 @@ -762,10 +761,10 @@ async def on_stats_update(stats): 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 @@ -775,7 +774,7 @@ async def on_stats_update(stats): # 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']})" @@ -797,11 +796,11 @@ async def on_stats_update(stats): 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__": diff --git a/demo_enhanced_api.py b/demo_enhanced_api.py index 692988b..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 @@ -15,35 +14,35 @@ 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("๐Ÿ”‘ 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("โœ… 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}") @@ -51,30 +50,30 @@ async def demo_enhanced_features(): 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: @@ -93,31 +92,37 @@ 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')}") + 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')}") + 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')}") + 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...") + 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): @@ -127,36 +132,36 @@ def on_authenticated(data): if i % 10 == 0 and i > 0: stats = persistent_client.get_connection_stats() print( - f" ๐Ÿ“ˆ [{i}s] Connected: {persistent_client.is_connected}, " + 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 "n1p5ah5u8t9438rbunpgrq0hlq" not in real_ssid: - print("๐Ÿ”‘ Real SSID detected - testing with live connection...") + print("Authentication: Real SSID detected - testing with live connection...") try: live_client = AsyncPocketOptionClient( @@ -165,40 +170,40 @@ def on_authenticated(data): 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("Note: Skipping live demo - requires real SSID") print( " Set environment variable: export POCKET_OPTION_SSID='your_complete_ssid'" ) @@ -209,56 +214,56 @@ def on_authenticated(data): 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:") @@ -287,23 +292,23 @@ 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") @@ -313,7 +318,7 @@ def show_keep_alive_features(): 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") @@ -322,7 +327,7 @@ def show_keep_alive_features(): 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") @@ -334,7 +339,7 @@ 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() @@ -346,18 +351,18 @@ async def main(): 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/examples/async_examples.py b/examples/async_examples.py deleted file mode 100644 index d9b7402..0000000 --- a/examples/async_examples.py +++ /dev/null @@ -1,304 +0,0 @@ -""" -Example usage of the Professional Async PocketOption API -""" - -import asyncio -import os -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, -) -from pocketoptionapi_async.utils import fetch_ssid - - -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 461cc43..385213f 100644 --- a/load_testing_tool.py +++ b/load_testing_tool.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 """ Load Testing and Stress Testing Tool for PocketOption Async API """ @@ -69,7 +68,7 @@ def __init__(self, ssid: str, is_demo: bool = True): async def run_load_test(self, config: LoadTestConfig) -> Dict[str, Any]: """Run comprehensive load test""" - logger.info("๐Ÿš€ Starting Load Test") + logger.info("Starting Load Test") logger.info( f"Config: {config.concurrent_clients} clients, {config.operations_per_client} ops/client" ) @@ -107,7 +106,7 @@ async def _run_standard_load_test(self, config: LoadTestConfig) -> Dict[str, Any 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) @@ -122,7 +121,7 @@ async def _run_standard_load_test(self, config: LoadTestConfig) -> Dict[str, Any 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 = [ @@ -134,7 +133,7 @@ async def _run_stress_test(self, config: LoadTestConfig) -> Dict[str, Any]: for phase_name, clients, delay in phases: logger.info( - f"๐Ÿ”ฅ Stress Phase: {phase_name} ({clients} clients, {delay}s delay)" + f"Stress: Stress Phase: {phase_name} ({clients} clients, {delay}s delay)" ) # Create tasks for this phase @@ -157,7 +156,7 @@ async def _run_stress_test(self, config: LoadTestConfig) -> Dict[str, Any]: 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(): @@ -207,7 +206,7 @@ async def _run_client_operations( ) ) - logger.info(f"โœ… Client {client_id} connected") + logger.info(f"Success: Client {client_id} connected") else: self._record_result( LoadTestResult( @@ -236,7 +235,7 @@ async def _run_client_operations( except Exception as e: logger.error( - f"โŒ Client {client_id} operation {op_num} failed: {e}" + f"Error: Client {client_id} operation {op_num} failed: {e}" ) self._record_result( LoadTestResult( @@ -250,7 +249,7 @@ async def _run_client_operations( ) except Exception as e: - logger.error(f"โŒ Client {client_id} failed: {e}") + logger.error(f"Error: Client {client_id} failed: {e}") finally: if client: @@ -316,11 +315,11 @@ async def _run_stress_client( except Exception as e: logger.error( - f"โŒ Stress client {client_id} operation {op_num} failed: {e}" + 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: @@ -442,7 +441,7 @@ async def _monitor_operations(self): 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}" + f"Statistics: Avg ops/sec (last 10s): {avg_ops:.1f}, Peak: {self.peak_operations_per_second}" ) except Exception as e: @@ -461,7 +460,7 @@ def _record_result(self, result: LoadTestResult): 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: @@ -472,7 +471,7 @@ async def _cleanup_clients(self): 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""" @@ -603,9 +602,9 @@ async def run_load_test_demo(ssid: str = None): 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.warning("Caution: Using demo SSID for load testing") - logger.info("๐Ÿš€ Starting Load Testing Demo") + logger.info("Starting Load Testing Demo") # Create load tester load_tester = LoadTester(ssid, is_demo=True) @@ -638,7 +637,7 @@ async def run_load_test_demo(ssid: str = None): 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: @@ -647,7 +646,7 @@ async def run_load_test_demo(ssid: str = None): # 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%}") @@ -659,7 +658,7 @@ async def run_load_test_demo(ssid: str = None): 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: @@ -699,16 +698,16 @@ async def run_load_test_demo(ssid: str = None): report_file = f"load_test_{i}_{timestamp}.json" 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: 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')}" diff --git a/migration_guide.py b/migration_guide.py deleted file mode 100644 index aec549d..0000000 --- a/migration_guide.py +++ /dev/null @@ -1,403 +0,0 @@ -""" -Migration script to convert old PocketOption API usage to new async API -""" - -import asyncio -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_tests.py b/performance_tests.py index 9fae6ec..b529c14 100644 --- a/performance_tests.py +++ b/performance_tests.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 """ Performance Tests for PocketOption Async API """ @@ -261,7 +260,7 @@ async def perform_operation(operation_id: int): 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) @@ -270,31 +269,35 @@ async def generate_performance_report(self) -> str: 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" + f"Success: Average Connection Time: {conn_results['avg_time']:.3f}s" ) report.append( - f"โœ… Min Connection Time: {conn_results['min_time']:.3f}s" + f"Success: Min Connection Time: {conn_results['min_time']:.3f}s" ) report.append( - f"โœ… Max Connection Time: {conn_results['max_time']:.3f}s" + 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" ) - report.append(f"โœ… Success Rate: {conn_results['success_rate']:.1f}%") - report.append(f"โœ… 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() @@ -305,30 +308,32 @@ async def generate_performance_report(self) -> str: 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}%" + f"Success: Success Rate: {concurrent_results['success_rate']:.1f}%" + ) + report.append( + f"Success: Operations/Second: {concurrent_results['operations_per_second']:.2f}" ) report.append( - f"โœ… Operations/Second: {concurrent_results['operations_per_second']:.2f}" + f"Success: Avg Operation Time: {concurrent_results['avg_operation_time']:.3f}s" ) report.append( - f"โœ… Avg Operation Time: {concurrent_results['avg_operation_time']:.3f}s" + f"Success: Total Time: {concurrent_results['total_time']:.3f}s" ) - report.append(f"โœ… 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) diff --git a/pocketoptionapi_async/client.py b/pocketoptionapi_async/client.py index e6a060e..ee1cc8d 100644 --- a/pocketoptionapi_async/client.py +++ b/pocketoptionapi_async/client.py @@ -159,7 +159,7 @@ async def connect( Returns: bool: True if connected successfully """ - logger.info("๐Ÿ”Œ Connecting to PocketOption...") + logger.info("Connecting to PocketOption...") # Update persistent setting if provided if persistent is not None: self.persistent_connection = persistent @@ -223,7 +223,7 @@ async def _start_regular_connection( 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() @@ -248,7 +248,7 @@ 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 @@ -294,15 +294,15 @@ async def _start_persistent_connection( 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()) @@ -328,15 +328,15 @@ async def _reconnection_monitor(self): await asyncio.sleep(30) # Check every 30 seconds if not self.is_connected: - logger.info("๐Ÿ”„ Connection lost, attempting reconnection...") + 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}") @@ -344,7 +344,7 @@ 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: @@ -456,7 +456,7 @@ async def get_candles( if not self.is_connected: if self.auto_reconnect: logger.info( - f"๐Ÿ”„ Connection lost, attempting reconnection for {asset} candles..." + f"Connection lost, attempting reconnection for {asset} candles..." ) reconnected = await self._attempt_reconnection() if not reconnected: @@ -498,13 +498,13 @@ async def get_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..." + 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}" + f" Reconnected, retrying candle request for {asset}" ) continue @@ -796,7 +796,7 @@ async def _wait_for_order_result( # 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) @@ -811,7 +811,7 @@ async def _wait_for_order_result( if request_id in self._active_orders: if self.enable_logging: logger.success( - f"โœ… Order {request_id} found in active tracking (final check)" + f" Order {request_id} found in active tracking (final check)" ) return self._active_orders[request_id] @@ -869,7 +869,7 @@ async def check_win( 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}" + f" Order {order_id} completed - Status: {result.status.value}, Profit: ${result.profit:.2f}" ) return { @@ -1025,7 +1025,7 @@ async def _on_json_data(self, data: Dict[str, Any]) -> None: self._candle_requests[request_id].set_result(candles) if self.enable_logging: logger.success( - f"โœ… Candles data received: {len(candles)} candles for {asset}" + f" Candles data received: {len(candles)} candles for {asset}" ) del self._candle_requests[request_id] return @@ -1060,7 +1060,7 @@ async def _on_json_data(self, data: Dict[str, Any]) -> None: self._active_orders[request_id] = order_result if self.enable_logging: logger.success( - f"โœ… Order {request_id} added to tracking from JSON data" + f" Order {request_id} added to tracking from JSON data" ) await self._emit_event("order_opened", data) @@ -1102,7 +1102,7 @@ async def _on_json_data(self, data: Dict[str, Any]) -> None: if self.enable_logging: logger.success( - f"โœ… Order {order_id} completed via JSON data: {status.value} - Profit: ${profit:.2f}" + f" Order {order_id} completed via JSON data: {status.value} - Profit: ${profit:.2f}" ) await self._emit_event("order_closed", result) @@ -1123,7 +1123,7 @@ 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") + logger.success(" Successfully authenticated with PocketOption") self._connection_stats["successful_connections"] += 1 await self._emit_event("authenticated", data) @@ -1137,7 +1137,7 @@ async def _on_balance_updated(self, data: Dict[str, Any]) -> None: ) self._balance = balance if self.enable_logging: - logger.info(f"๐Ÿ’ฐ Balance updated: ${balance.balance:.2f}") + logger.info(f"Balance updated: ${balance.balance:.2f}") await self._emit_event("balance_updated", balance) except Exception as e: if self.enable_logging: @@ -1151,7 +1151,7 @@ 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}") + logger.info(f"Order opened: {data}") await self._emit_event("order_opened", data) async def _on_order_closed(self, data: Dict[str, Any]) -> None: @@ -1219,7 +1219,7 @@ async def _on_candles_received(self, data: Dict[str, Any]) -> None: async def _on_disconnected(self, data: Dict[str, Any]) -> None: """Handle disconnection event""" if self.enable_logging: - logger.warning("๐Ÿ”Œ Disconnected from PocketOption") + logger.warning("Disconnected from PocketOption") await self._emit_event("disconnected", data) async def _handle_candles_stream(self, data: Dict[str, Any]) -> None: @@ -1258,7 +1258,7 @@ async def _handle_candles_stream(self, data: Dict[str, Any]) -> None: 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)""" @@ -1385,11 +1385,11 @@ async def _attempt_reconnection(self, max_attempts: int = 3) -> bool: 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: @@ -1407,18 +1407,18 @@ async def _attempt_reconnection(self, max_attempts: int = 3) -> bool: 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", {}) 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}" + f"Reconnection attempt {attempt + 1} failed with error: {e}" ) - logger.error(f"โŒ All {max_attempts} reconnection attempts failed") + logger.error(f"All {max_attempts} reconnection attempts failed") return False 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/models.py b/pocketoptionapi_async/models.py index 1cbe476..e976917 100644 --- a/pocketoptionapi_async/models.py +++ b/pocketoptionapi_async/models.py @@ -10,11 +10,27 @@ 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" @@ -24,14 +40,48 @@ class OrderStatus(str, Enum): 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 @@ -44,7 +94,11 @@ class Config: 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" @@ -56,7 +110,11 @@ class Config: 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 @@ -65,16 +123,24 @@ class Candle(BaseModel): close: float volume: Optional[float] = None asset: str - timeframe: int # in seconds + timeframe: int # in seconds, representing the duration of the candle @validator("high") def high_must_be_valid(cls, v, values): + """ + 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") def low_must_be_valid(cls, v, values): + """ + 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 @@ -84,29 +150,44 @@ class Config: 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") 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") return v @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") 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 @@ -125,7 +206,11 @@ class Config: 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 @@ -137,7 +222,11 @@ class Config: 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 diff --git a/pocketoptionapi_async/utils.py b/pocketoptionapi_async/utils.py index 244e21b..86f93f7 100644 --- a/pocketoptionapi_async/utils.py +++ b/pocketoptionapi_async/utils.py @@ -11,29 +11,6 @@ from .models import Candle, OrderResult -async def fetch_ssid(): - urls = [ - "wss://ws.pocketoption.com/socket.io/?EIO=3&transport=websocket", - "wss://ws-demo.pocketoption.com/socket.io/?EIO=3&transport=websocket", - ] - - for uri in urls: - try: - async with websockets.connect(uri) as websocket: - while True: - message = await websocket.recv() - if message.startswith('42["auth"'): - return message - elif message == '2': - await websocket.send('3') - elif message.startswith('0'): - sid = message.split('"sid":"')[1].split('"')[0] - await websocket.send(f'2/{sid}') - except Exception as e: - print(f"Failed to connect to {uri}: {e}") - continue - - raise Exception("Unable to fetch SSID from any endpoint") def format_session_id( session_id: str, @@ -69,17 +46,7 @@ def format_session_id( return f'42["auth",{json.dumps(auth_data)}]' -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 - ) + def calculate_payout_percentage( entry_price: float, exit_price: float, direction: str, payout_rate: float = 0.8 ) -> float: 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 31a0f7e..0000000 --- a/setup.py +++ /dev/null @@ -1,70 +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 b87f97e..0000000 --- a/test.py +++ /dev/null @@ -1,44 +0,0 @@ -import dotenv -from pocketoptionapi_async import AsyncPocketOptionClient -import os -dotenv.load_dotenv() - -#ssid = r'42["auth",{"session":"t04ppgptp3404h0lajp4bo7smh","isDemo":1,"uid":101884312,"platform":2,"isFastHistory":true}]' -ssid = 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...") diff --git a/tests/enhanced_test.py b/tests/enhanced_test.py index 20f1e64..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 """ @@ -38,7 +37,7 @@ async def handle_error_alert(self, alert_data): 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( @@ -52,7 +51,7 @@ async def test_enhanced_connection(self): ) if success: - logger.success("โœ… Connection successful with monitoring") + logger.success(" Connection successful with monitoring") # Get health status health = await client.get_health_status() @@ -68,16 +67,16 @@ async def test_enhanced_connection(self): 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()), @@ -94,10 +93,10 @@ async def test_monitored_operations(self, client): ) 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( @@ -144,13 +143,13 @@ async def working_operation(): # 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): @@ -202,13 +201,13 @@ async def create_and_test_client(client_id: int): 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" 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") + logger.info(f" Avg Client Time: {avg_time:.3f}s") async def test_error_monitoring(self): """Test error monitoring capabilities""" @@ -235,7 +234,7 @@ async def test_error_monitoring(self): # 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]}") @@ -309,7 +308,7 @@ async def generate_performance_report(self): report.append("โ€ข System health issues detected - check service status") if not error_summary["top_errors"]: - report.append("โ€ข โœ… No significant errors detected") + report.append("โ€ข No significant errors detected") report.append("") report.append("=" * 80) @@ -325,7 +324,7 @@ async def generate_performance_report(self): 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 = [ @@ -337,12 +336,12 @@ async def run_all_tests(self): 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() @@ -350,15 +349,15 @@ async def run_all_tests(self): 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() @@ -367,7 +366,7 @@ async def main(): if session_id == "test_session_id": logger.warning( - "โš ๏ธ Using test session ID - set POCKET_OPTION_SSID for real testing" + " Using test session ID - set POCKET_OPTION_SSID for real testing" ) # Create and run enhanced tester diff --git a/tests/integration_tests.py b/tests/integration_tests.py index 6ca52bd..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 @@ -28,7 +27,7 @@ def __init__(self, ssid: str): 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 @@ -60,7 +59,7 @@ async def run_full_integration_tests(self) -> Dict[str, Any]: "details": result, } - status_emoji = "โœ…" if result["success"] else "โŒ" + status_emoji = "" if result["success"] else "โŒ" logger.info( f"{status_emoji} {phase_name}: {'PASSED' if result['success'] else 'FAILED'} ({duration:.2f}s)" ) @@ -900,9 +899,9 @@ async def run_integration_tests(ssid: str = None): 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.warning("Using demo SSID for integration testing") - logger.info("๐ŸŽฏ PocketOption API Integration Testing Suite") + logger.info("PocketOption API Integration Testing Suite") logger.info("=" * 60) logger.info("This comprehensive test validates all components working together") logger.info("") @@ -919,7 +918,7 @@ async def run_integration_tests(ssid: str = None): 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%}") @@ -933,7 +932,7 @@ async def run_integration_tests(ssid: str = None): logger.info("-" * 30) assessment = report["system_assessment"] for aspect, details in assessment.items(): - status_emoji = "โœ…" if details["status"] == "GOOD" else "โš ๏ธ" + status_emoji = "" if details["status"] == "GOOD" else "โš ๏ธ" logger.info( f"{status_emoji} {aspect.title()}: {details['score']:.0f}/100 - {details['details']}" ) @@ -961,16 +960,16 @@ async def run_integration_tests(ssid: str = None): "๐Ÿ‘ 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") + logger.warning("FAIR: System has some issues that should be addressed") else: logger.error( - "โŒ POOR: System has significant issues requiring immediate attention" + "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 diff --git a/tests/test_balance_fix.py b/tests/test_balance_fix.py index 4cfc3ce..56c1f9c 100644 --- a/tests/test_balance_fix.py +++ b/tests/test_balance_fix.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 """ Test script to verify the balance issue fix """ @@ -13,7 +12,7 @@ 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 @@ -29,7 +28,7 @@ async def test_balance_fix(): def on_balance_updated(balance): nonlocal balance_received balance_received = True - logger.success(f"โœ… Balance callback triggered: ${balance.balance:.2f}") + logger.success(f" Balance callback triggered: ${balance.balance:.2f}") client.add_event_callback("balance_updated", on_balance_updated) @@ -38,48 +37,48 @@ def on_balance_updated(balance): 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}" + 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 diff --git a/tests/test_candles_fix.py b/tests/test_candles_fix.py index 002938e..921d718 100644 --- a/tests/test_candles_fix.py +++ b/tests/test_candles_fix.py @@ -14,7 +14,7 @@ async def test_candles_retrieval(): # 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: @@ -38,10 +38,10 @@ async def test_candles_retrieval(): 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')} - " @@ -52,20 +52,20 @@ async def test_candles_retrieval(): 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...") @@ -75,11 +75,11 @@ async def test_candles_retrieval(): 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"] @@ -90,17 +90,17 @@ async def test_candles_retrieval(): if test_candles: latest = test_candles[-1] if test_candles else None print( - f"โœ… {test_asset}: Latest price {latest.close:.5f}" + f" {test_asset}: Latest price {latest.close:.5f}" if latest - else f"โœ… {test_asset}: {len(test_candles)} candles" + 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() @@ -108,7 +108,7 @@ async def test_candles_retrieval(): finally: try: await client.disconnect() - print("\n๐Ÿ”Œ Disconnected from PocketOption") + print("\nDisconnected from PocketOption") except: pass @@ -151,7 +151,7 @@ async def test_candles_message_format(): if __name__ == "__main__": - print("๐Ÿงช PocketOption Candles Test Suite") + print("PocketOption Candles Test Suite") print("=" * 40) # Test message format first @@ -159,7 +159,7 @@ async def 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) diff --git a/tests/test_complete_order_tracking.py b/tests/test_complete_order_tracking.py index b94b78a..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 @@ -19,11 +18,11 @@ async def wait_for_trade_completion(): 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 @@ -35,10 +34,10 @@ async def wait_for_trade_completion(): 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) @@ -46,9 +45,9 @@ async def wait_for_trade_completion(): # 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 = [] @@ -62,12 +61,12 @@ def on_order_closed(order_result): if order_result.profit < 0 else "EVEN" ) - print(f"๐ŸŽฏ Order completed: {status} - Profit: ${order_result.profit:.2f}") + 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("\n๐Ÿ“ˆ Placing test order...") + print("\nPlacing test order...") order_result = await client.place_order( asset="EURUSD_otc", amount=1.0, @@ -75,7 +74,7 @@ def on_order_closed(order_result): 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}") @@ -86,9 +85,9 @@ def on_order_closed(order_result): # Check immediate order result 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") 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 @@ -127,7 +126,7 @@ def on_order_closed(order_result): if result.profit < 0 else "EVEN" ) - print("\n๐ŸŽฏ TRADE COMPLETED!") + print("\nTRADE COMPLETED!") print(f" Result: {win_lose}") print(f" Profit/Loss: ${result.profit:.2f}") if result.payout: @@ -147,14 +146,14 @@ def on_order_closed(order_result): ) 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("\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}") @@ -167,25 +166,25 @@ def on_order_closed(order_result): if final_result.profit is not None: print(f" Final Profit/Loss: ${final_result.profit:.2f}") else: - print(" โš ๏ธ No profit data available (may indicate tracking issue)") + print(" No profit data available (may indicate tracking issue)") else: - print("\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("\n๐Ÿ”Œ Disconnecting...") + print("\nDisconnecting...") await client.disconnect() - print("โœ… Test completed") + print(" Test completed") if __name__ == "__main__": diff --git a/tests/test_complete_ssid.py b/tests/test_complete_ssid.py index 8250a12..58a7400 100644 --- a/tests/test_complete_ssid.py +++ b/tests/test_complete_ssid.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 """ Test script demonstrating complete SSID format handling """ @@ -12,7 +11,7 @@ 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) @@ -29,30 +28,30 @@ async def test_complete_ssid_format(): # 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) @@ -71,17 +70,17 @@ async def test_complete_ssid_format(): 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(): @@ -94,7 +93,7 @@ async def test_real_connection(): 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}]\'' @@ -106,38 +105,38 @@ async def test_real_connection(): 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() diff --git a/tests/test_demo_live_connection.py b/tests/test_demo_live_connection.py index 1e8e9d0..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 """ @@ -17,7 +16,7 @@ async def test_demo_live_connection(): 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}") @@ -27,20 +26,20 @@ async def test_demo_live_connection(): success = await asyncio.wait_for(client_demo.connect(), timeout=30) if success: - print(" โœ… Connected successfully!") + 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(" โŒ Connection failed") + print(" Connection failed") except asyncio.TimeoutError: 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}") @@ -50,25 +49,25 @@ async def test_demo_live_connection(): success = await asyncio.wait_for(client_live.connect(), timeout=30) if success: - print(" โœ… Connected successfully!") + 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(" โŒ Connection failed") + print(" Connection failed") except asyncio.TimeoutError: 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__": diff --git a/tests/test_demo_live_fix.py b/tests/test_demo_live_fix.py index de291f5..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 """ @@ -14,11 +13,11 @@ async def test_demo_live_fix(): # 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 @@ -27,10 +26,10 @@ async def test_demo_live_fix(): 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 @@ -39,10 +38,10 @@ async def test_demo_live_fix(): 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 @@ -53,10 +52,10 @@ async def test_demo_live_fix(): 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") + print("\nTest: Raw session with is_demo=False") client_raw_live = AsyncPocketOptionClient( ssid=raw_session, is_demo=False, uid=72645361 ) @@ -66,7 +65,7 @@ async def test_demo_live_fix(): 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") @@ -95,7 +94,7 @@ async def test_demo_live_fix(): 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 @@ -106,7 +105,7 @@ async def test_demo_live_fix(): 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]) diff --git a/tests/test_fixed_connection.py b/tests/test_fixed_connection.py index 2d25ab2..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 """ @@ -38,20 +37,20 @@ async def test_connection_fix(): 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'}" @@ -62,33 +61,33 @@ async def test_connection_fix(): 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(f" Balance request failed (expected): {e}") - print("\nโœ… All connection tests passed!") + 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)" + " 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 @@ -98,7 +97,7 @@ 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:") @@ -110,26 +109,26 @@ async def test_old_vs_new_comparison(): 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 @@ -140,7 +139,7 @@ async def main(): print("=" * 60) if success: - print("โœ… CONNECTION FIX VERIFICATION COMPLETE") + print(" CONNECTION FIX VERIFICATION COMPLETE") print( "๐Ÿ“ The new async API now follows the same handshake pattern as the old API" ) @@ -150,7 +149,7 @@ async def main(): print(" โ€ข Authentication event handling") print(" โ€ข Error handling with timeouts") else: - print("โŒ CONNECTION FIX NEEDS MORE WORK") + print("CONNECTION FIX NEEDS MORE WORK") print("=" * 60) diff --git a/tests/test_new_api.py b/tests/test_new_api.py index 5126263..d505419 100644 --- a/tests/test_new_api.py +++ b/tests/test_new_api.py @@ -16,7 +16,7 @@ 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) @@ -27,7 +27,7 @@ async def test_basic_functionality(): if "n1p5ah5u8t9438rbunpgrq0hlq" in complete_ssid: print( - "โš ๏ธ Using mock SSID. Set POCKET_OPTION_SSID environment variable for live testing." + " 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}]\'' @@ -35,33 +35,33 @@ async def test_basic_functionality(): try: # Test 1: Client initialization - print("\n1๏ธโƒฃ Testing client initialization...") + print("\nTesting client initialization...") client = AsyncPocketOptionClient(ssid=complete_ssid, is_demo=True) - print("โœ… Client initialized successfully") + 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 ) - 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...") @@ -72,30 +72,30 @@ async def test_basic_functionality(): direction=OrderDirection.CALL, 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(f"Unexpected error: {e}") - print("\n๐ŸŽฏ API Structure Tests") + print("\nAPI Structure Tests") print("=" * 30) # Test API structure test_api_structure() - print("\nโœ… All tests completed!") - print("\n๐Ÿ“– Next steps:") + print("\n All tests completed!") + print("\nNext steps:") print( " 1. Set your real session ID: $env:POCKET_OPTION_SSID='your_real_session_id'" ) @@ -120,20 +120,20 @@ def test_api_structure(): 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: @@ -141,7 +141,7 @@ def test_api_structure(): 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: @@ -151,12 +151,12 @@ def test_api_structure(): direction=OrderDirection.CALL, 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(): @@ -168,11 +168,11 @@ async def test_context_manager(): 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(): @@ -191,32 +191,32 @@ def test_callback(data): callback_called = True client.add_event_callback("test_event", test_callback) - print("โœ… Event callback registered") + print(" Event callback registered") # Test callback removal client.remove_event_callback("test_event", test_callback) - print("โœ… Event callback removed") + 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: diff --git a/tests/test_order_fix.py b/tests/test_order_fix.py index 77370e1..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 """ @@ -16,11 +15,11 @@ async def test_order_placement(): 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) @@ -28,14 +27,14 @@ async def test_order_placement(): 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", @@ -44,7 +43,7 @@ async def test_order_placement(): duration=60, ) - logger.success("โœ… 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}") @@ -52,26 +51,26 @@ async def test_order_placement(): 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)" + "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 05d0698..e29ddd6 100644 --- a/tests/test_order_logging_fixes.py +++ b/tests/test_order_logging_fixes.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 """ Test Order Tracking and Logging Fixes """ @@ -17,13 +16,13 @@ async def test_fixes(): 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: @@ -32,10 +31,10 @@ async def test_fixes(): 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) @@ -43,15 +42,15 @@ async def test_fixes(): # 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 ) - 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'}") @@ -60,9 +59,9 @@ async def test_fixes(): 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) @@ -72,7 +71,7 @@ async def test_fixes(): 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() @@ -80,7 +79,7 @@ async def test_fixes(): 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: @@ -89,10 +88,10 @@ async def test_fixes(): 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) @@ -100,15 +99,15 @@ async def test_fixes(): # 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 ) - 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'}") @@ -117,14 +116,14 @@ async def test_fixes(): 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__": diff --git a/tests/test_order_placement_fix.py b/tests/test_order_placement_fix.py index 21c320c..6d30569 100644 --- a/tests/test_order_placement_fix.py +++ b/tests/test_order_placement_fix.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python3 """ Test script to verify order placement fix """ @@ -25,10 +24,10 @@ async def test_order_placement_fix(): 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) @@ -45,7 +44,7 @@ async def test_order_placement_fix(): ) logger.success( - f"โœ… Order created successfully with request_id: {test_order.request_id}" + 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}") @@ -54,20 +53,18 @@ async def test_order_placement_fix(): # 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") + 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: @@ -79,28 +76,26 @@ async def test_order_placement_fix(): ) logger.success( - f"โœ… Order placement succeeded: {order_result.order_id}" + 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.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() diff --git a/tests/test_order_tracking_complete.py b/tests/test_order_tracking_complete.py index 4e53c38..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: @@ -23,11 +22,11 @@ async def test_complete_order_lifecycle(): 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 @@ -39,10 +38,10 @@ async def test_complete_order_lifecycle(): 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) @@ -50,9 +49,9 @@ async def test_complete_order_lifecycle(): # 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("\n๐Ÿ“‹ TEST 1: Order Placement Without Duplication") @@ -63,7 +62,7 @@ async def test_complete_order_lifecycle(): print(f"๐Ÿ“Š Initial active orders: {len(initial_active)}") # Place order - print("๐Ÿ“ˆ Placing order...") + print("Placing order...") order_result = await client.place_order( asset="EURUSD_otc", amount=1.0, @@ -71,7 +70,7 @@ async def test_complete_order_lifecycle(): 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}") @@ -87,9 +86,9 @@ async def test_complete_order_lifecycle(): 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}") @@ -100,11 +99,11 @@ async def test_complete_order_lifecycle(): # 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 @@ -124,7 +123,7 @@ def on_order_closed(order_result): else "EVEN" ) print( - f"๐ŸŽฏ ORDER COMPLETED via EVENT: {status} - Profit: ${order_result.profit:.2f}" + f"ORDER COMPLETED via EVENT: {status} - Profit: ${order_result.profit:.2f}" ) client.add_event_callback("order_closed", on_order_closed) @@ -168,7 +167,7 @@ def on_order_closed(order_result): if result.profit < 0 else "EVEN" ) - print("\n๐ŸŽฏ TRADE COMPLETED!") + print("\nTRADE COMPLETED!") print(f" Result: {win_lose}") print(f" Profit/Loss: ${result.profit:.2f}") if result.payout: @@ -188,7 +187,7 @@ def on_order_closed(order_result): ) else: - print(" โŒ Order disappeared from tracking system") + print(" Order disappeared from tracking system") break await asyncio.sleep(2) # Check every 2 seconds @@ -199,21 +198,21 @@ def on_order_closed(order_result): # Check if we completed via event callback if completed_orders: - print("โœ… 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("โš ๏ธ 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("โœ… Order completion detected via POLLING!") + 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("โŒ Order not found via polling") + print("Order not found via polling") # Test 7: Final System State print("\n๐Ÿ“‹ TEST 7: Final System State") @@ -235,62 +234,62 @@ def on_order_closed(order_result): # 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("Balance Retrieval: FAIL") - print(f"\n๐ŸŽฏ OVERALL RESULT: {tests_passed}/{total_tests} tests passed") + 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("\n๐Ÿ”Œ Disconnecting...") + print("\nDisconnecting...") await client.disconnect() - print("โœ… Test completed") + print(" Test completed") if __name__ == "__main__": diff --git a/tests/test_order_tracking_fix.py b/tests/test_order_tracking_fix.py index 90ab49c..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 @@ -19,10 +18,10 @@ async def test_order_tracking(): 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) @@ -33,10 +32,10 @@ async def test_order_tracking(): 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) @@ -44,17 +43,17 @@ async def test_order_tracking(): # 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 ) - 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}") @@ -66,13 +65,13 @@ async def test_order_tracking(): 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 @@ -103,12 +102,10 @@ async def test_order_tracking(): # 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 @@ -118,21 +115,21 @@ async def test_order_tracking(): 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__": diff --git a/tests/test_persistent_connection.py b/tests/test_persistent_connection.py index 1eb7608..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 @@ -14,15 +13,15 @@ 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() @@ -34,7 +33,7 @@ async def test_persistent_connection(): if "n1p5ah5u8t9438rbunpgrq0hlq" in complete_ssid: print( - "โ„น๏ธ Using test SSID - connection will fail but demonstrates the keep-alive logic" + " Using test SSID - connection will fail but demonstrates the keep-alive logic" ) print( " For real testing, set: export POCKET_OPTION_SSID='your_complete_ssid'" @@ -57,7 +56,7 @@ async def test_persistent_connection(): 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...") @@ -67,23 +66,23 @@ async def test_persistent_connection(): if i % 10 == 0: stats = client_regular.get_connection_stats() print( - f" ๐Ÿ“ˆ Stats: Connected={client_regular.is_connected}, " + 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: @@ -103,11 +102,11 @@ def on_connected(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("โœ… Event: Authenticated") + print(" Event: Authenticated") client_persistent.add_event_callback("connected", on_connected) client_persistent.add_event_callback("reconnected", on_reconnected) @@ -117,7 +116,7 @@ def on_authenticated(data): 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...") @@ -130,7 +129,7 @@ def on_authenticated(data): if i % 15 == 0 and i > 0: stats = client_persistent.get_connection_stats() print( - f" ๐Ÿ“ˆ Stats: Connected={client_persistent.is_connected}, " + 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)}, " @@ -162,13 +161,13 @@ def on_authenticated(data): 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() @@ -194,7 +193,7 @@ def on_authenticated(data): 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...") @@ -204,37 +203,37 @@ def on_authenticated(data): if i % 30 == 0: stats = resilience_client.get_connection_stats() print( - f" ๐Ÿ“ˆ Stats: Connected={resilience_client.is_connected}, " + 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") @@ -272,7 +271,7 @@ 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()) diff --git a/tests/test_ssid_formats.py b/tests/test_ssid_formats.py index c012be1..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 """ @@ -11,49 +10,49 @@ 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 ) - 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): @@ -75,31 +74,31 @@ def extract_auth_data(msg): ) 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(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("\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( @@ -119,7 +118,7 @@ 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