Skip to content

init 0.2.6, semi-major revamp#60

Merged
Rick-29 merged 24 commits intoChipaDevTeam:masterfrom
sixtysixx:master
Feb 13, 2026
Merged

init 0.2.6, semi-major revamp#60
Rick-29 merged 24 commits intoChipaDevTeam:masterfrom
sixtysixx:master

Conversation

@sixtysixx
Copy link
Contributor

@sixtysixx sixtysixx commented Feb 13, 2026

Fix tests, examples, history, formatted, CI refactor, SSID parsing patches.

Added functions - shutdown, get_assets, some raw / lower level websocket stuff, and i think thats it idk i forget ngl

Summary by CodeRabbit

  • New Features

    • Pending order flows (create/list), deal end time, active asset listing, wait-for-assets API, non-consuming shutdown/close, and raw messaging/iterator APIs
    • Enhanced real-time candle subscriptions and improved history/get-candles capabilities
  • Bug Fixes & Improvements

    • Decimal precision for amounts/balances, longer timeouts, more resilient region/SSID selection and connection handling
    • More robust WebSocket/event routing, lifecycle and logging improvements
  • Documentation

    • Streamlined issue/PR templates and new contributor/product/tech-stack guides; updated changelog and README

sixtysixx and others added 16 commits February 9, 2026 22:29
…ion optimizations

- Integrate RunnerCommand channel across all API modules for unified shutdown signaling
- Add non-consuming shutdown_ref() method to Client and rename shutdown to shutdown_owned()
- Implement connection stability tracking with automatic reconnect attempt reset after 10 seconds of uptime
- Optimize timeout configuration: extend initial handshake to 20s, reduce WebSocket connection timeout to 10s
- Refine logging verbosity: downgrade connection lifecycle events from info to debug for production
- Update region configuration data with precise geographic coordinates and streamlined endpoint lists
- Modernize test infrastructure using module-scoped fixtures and reduced sleep intervals
…connection error type

- Changed WebsocketConnectionError to use Box<TungsteniteError> for better error handling.
- Added From<TungsteniteError> implementation for BinaryOptionsToolsError.
- Cleaned up imports and improved error messages for clarity.

Remove unused imports in send.rs

- Removed the unused `debug` import from tracing.

Refactor RecieverStream and FilteredRecieverStream in stream.rs

- Cleaned up code formatting and improved readability.
- Ensured consistent use of async/await patterns.
- Updated error handling in receive methods.

Update traits in traits.rs

- Cleaned up imports and ensured consistent formatting.
- Maintained trait definitions for better clarity and usability.

Refactor Data and Callback structures in types.rs

- Improved code organization and readability.
- Ensured consistent use of async patterns and error handling.
- Updated the default_validator function for clarity.

Update reimports in reimports.rs

- Organized imports for better readability.

Bump binary-options-tools-macros version to 0.2.0

- Updated version in Cargo.lock to reflect the latest changes.
…eals module panic and ExpertOptions deserialization.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 13, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • ✅ Review completed - (🔄 Check again to review again)
📝 Walkthrough

Walkthrough

Significant refactor: repository templates and CI streamlined; monetary values migrated from f64 to rust_decimal::Decimal and timestamps to i64 across crates and Python bindings; new RunnerCommand and ResponseRouter wiring threaded through modules; pending-order APIs, richer Socket.IO binary/text handling, centralized TLS connector/geolocation, and a redesigned event-driven WebSocket client introduced.

Changes

Cohort / File(s) Summary
Repository templates & CI
​.github/ISSUE_TEMPLATE/bug_report.md, ​.github/ISSUE_TEMPLATE/documentation.md, ​.github/ISSUE_TEMPLATE/feature_request.md, ​.github/ISSUE_TEMPLATE/question.md, ​.github/PULL_REQUEST_TEMPLATE.md, ​.github/workflows/CI.yml, ​.github/workflows/docs.yml
Streamlined/shortened issue and PR templates; CI changes for musllinux path (inline docker run, use python -m venv), docs job installs mkdocs-material and mkdocstrings[python].
Python package surface & stubs
BinaryOptionsToolsV2/python/.../__init__.py, BinaryOptionsToolsV2/python/.../BinaryOptionsToolsV2.pyi, BinaryOptionsToolsV2/python/.../config.py, .../tracing.py, .../validator.py, .../pocketoption/asynchronous.py, .../pocketoption/synchronous.py
New/rewritten package init with Rust-extension re-export fallback; added high-level Python APIs (open_pending_order, raw order helpers, shutdown, active_assets, pending-deals accessors); updated config defaults and tracing/validator runtime resolution; pyi signatures adjusted to match Rust changes.
UniFFI PocketOption bindings
BinaryOptionsToolsUni/src/platforms/pocketoption/{client.rs,types.rs}, BinaryOptionsUni/src/error.rs
Added UniFFI public types (PendingOrder, Deal, Candle, Action, AssetType, etc.) and From conversions; client gained open_pending_order and decimal-aware amount handling; UniError got General(String).
Dependency & version bumps
BinaryOptionsToolsUni/Cargo.toml, BinaryOptionsToolsV2/Cargo.toml, crates/*/Cargo.toml
Bumped many crate/package versions to 0.2.x, updated binary_options_tools dep versions, and added rust_decimal / rust_decimal_macros and supporting deps.
Numeric/time migration (Decimal & i64)
crates/binary_options_tools/src/..., crates/.../pocketoption/{types.rs,candle.rs,pocket_client.rs,state.rs,utils.rs}, BinaryOptionsToolsV2/src/framework.rs, crates/.../framework/{market.rs,virtual_market.rs}
Large migration f64→Decimal and timestamp f64→i64 across traits, structs and APIs (Market, VirtualMarket, trades, deals, candles, historical data, state, pocket_client, modules); added f64_to_decimal helper and decimal serde handling.
RunnerCommand & module wiring
crates/core-pre/src/traits.rs, crates/core-pre/src/{builder.rs,client.rs}, crates/core-pre/examples/*, crates/binary_options_tools/src/pocketoption/modules/*
Introduced RunnerCommand enum and threaded AsyncSender<RunnerCommand> through ApiModule::new / LightweightModule::new and many module constructors to enable inter-module runner control (Disconnect/Shutdown/Connect/Reconnect).
Subscriptions & response multiplexing
crates/binary_options_tools/src/pocketoption/modules/subscriptions.rs
Replaced per-wait receivers with a ResponseRouter keyed by command_id (Uuid); Commands/Responses include command_id; handles/streams use shared router to multiplex and await responses.
Pending orders & historical data
crates/binary_options_tools/src/pocketoption/modules/{pending_trades.rs,historical_data.rs}, BinaryOptionsToolsUni/src/platforms/pocketoption/types.rs
Added pending-order flows and types, increased timeouts (10→30s), improved Socket.IO binary/text parsing, introduced state machines for request/response matching (ExpectedMessage/RequestType).
Connection, connector & SSID/regions
crates/binary_options_tools/src/pocketoption/{connect.rs,utils.rs,ssid.rs,regions.rs}
Switched to sequential try_connect with fallback URLs and jitter; centralized TLS Connector via OnceLock; multi-provider public IP/geolocation; SSID parsing now preserves raw/json and exposes helpers (current_url, session_id, ip_address, demo); added region helpers.
Assets & expertoptions
crates/binary_options_tools/src/expertoptions/*, crates/binary_options_tools/src/pocketoption/modules/assets.rs
Asset.symbol became Option<String> with Asset::get_symbol() helper; asset update routing moved to MultiPatternRule; message parsing improved for 1‑step Socket.IO frames; logging level tweaks.
WebSocket client architecture
crates/core/data/{client2.rs,client_enhanced.rs}
Major redesign to event-driven WebSocket client: new WebSocketEvent variants, SharedState, KeepAliveManager, ConnectionState, prioritized receivers, batching, handler/event interfaces, and richer lifecycle and reconnection handling.
Utilities, tests & docs
crates/binary_options_tools/src/utils/mod.rs, crates/binary_options_tools/src/validator.rs, .gitignore, crates/*/data/*.json, README/CHANGELOG, new docs files (guidelines, product, tech-stack, UNIMPLEMENTED.md)`
Added f64_to_decimal helper and tests; RawValidator derives Default; updated region JSONs; many docs/README/CHANGELOG edits and new project docs; minor .gitignore reformatting.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Python Client
    participant PyRaw as RawPocket (PyO3/Rust)
    participant Runner as Runner/Dispatcher
    participant WS as WebSocket Server

    Client->>PyRaw: open_pending_order(params)
    PyRaw->>Runner: enqueue Command(OpenPendingOrder, command_id)
    Runner->>WS: send Socket.IO request (42[...] / binary)
    WS-->>Runner: response (text or binary)
    Runner->>Runner: ResponseRouter routes by command_id
    Runner-->>PyRaw: deliver command response (PendingOrder)
    PyRaw-->>Client: return PendingOrder/result
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested reviewers

  • Rick-29
  • theshadow76

"🐰
I hopped through Decimal fields so bright,
Timestamps now steady in integer light,
RunnerCommand whispers to WebSocket and thread,
Pending orders hop out — precise and well-bred,
A tiny rabbit cheers v0.2.x tonight!"

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'init 0.2.6, semi-major revamp' directly reflects the primary change: a version bump to 0.2.6 with significant refactoring across the codebase including Rust core updates, Python bindings, decimal-based precision, and API surface expansions.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into master

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

No actionable comments were generated in the recent review. 🎉


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @sixtysixx, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a semi-major revamp to the BinaryOptionsTools library, primarily focusing on enhancing financial precision, improving WebSocket communication robustness, and expanding trading capabilities with pending orders. The core Rust logic now leverages Decimal types for all financial calculations, mitigating floating-point inaccuracies. Significant effort was put into refining WebSocket message parsing and handling, making the client more resilient to various server responses and network conditions. The Python bindings have been thoroughly updated to expose these new features and improvements, ensuring a consistent and reliable experience across language interfaces. Additionally, internal architectural changes improve module communication and error handling, laying a stronger foundation for future development.

Highlights

  • Enhanced Financial Precision: Financial values across the library, including trade amounts, prices, and balances, have been migrated from f64 (floating-point numbers) to Decimal types. This change significantly improves accuracy and prevents potential precision errors inherent in floating-point arithmetic, which is crucial for financial applications.
  • Improved WebSocket Robustness and Parsing: The WebSocket message handling has been significantly refactored to be more resilient. This includes robust SSID parsing to correctly handle complex PHP serialized session objects and sanitized Socket.IO frames, improved handling of 1-step Socket.IO messages, and better detection of binary placeholders. Connection attempts now include jittered delays to prevent overwhelming servers during reconnections.
  • New Pending Order Functionality: The PocketOption client now supports opening and managing pending orders, allowing users to set trades that execute when specific market conditions are met. This adds a new layer of automated trading capability.
  • Refined API Module Architecture: API modules now receive a RunnerCommand sender, enabling them to signal the main client runner for actions like graceful shutdown. This enhances control flow and error handling within the modular architecture.
  • Comprehensive Python Bindings Update: The Python bindings have been updated to reflect all underlying Rust changes, including new methods for pending orders, improved SSID sanitization, and enhanced logging configuration. The Python package structure was also reorganized for better maintainability.
  • Updated Issue and PR Templates: GitHub issue and pull request templates have been refactored to streamline reporting and contribution processes, making it easier for users to provide necessary information.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • BinaryOptionsToolsUni/Cargo.toml
    • Updated binary_options_tools dependency to version 0.2.0.
  • BinaryOptionsToolsUni/README.md
    • Corrected a heading from 'C#' to 'C'.
  • BinaryOptionsToolsUni/src/error.rs
    • Added a new General error variant to UniError.
  • BinaryOptionsToolsUni/src/platforms/pocketoption/client.rs
    • Updated balance method to return f64 by converting from Decimal.
    • Modified trade, buy, and sell methods to accept f64 amounts and convert them to Decimal internally.
    • Introduced open_pending_order and get_pending_deals methods for managing pending trades.
  • BinaryOptionsToolsUni/src/platforms/pocketoption/types.rs
    • Introduced PendingOrder struct for representing pending trade orders.
    • Updated Deal struct fields (amount, profit, open_price, close_price, amount_usd, amount_usd2) to use f64 by converting from Decimal.
    • Updated Candle struct fields (timestamp, open, high, low, close, volume) to use f64 by converting from Decimal and i64 for timestamp.
  • BinaryOptionsToolsV2/Cargo.lock
    • Updated BinaryOptionsToolsV2 version to 0.2.6.
    • Added rust_decimal and rust_decimal_macros as dependencies.
    • Updated various dependency versions including aws-lc-sys, binary-options-tools-core-pre, binary-options-tools-macros, binary_options_tools, libc, ryu, toml_parser, unicode-ident, and zmij.
  • BinaryOptionsToolsV2/Cargo.toml
    • Updated package version to 0.2.6.
    • Updated binary_options_tools dependency to version 0.2.0.
    • Added rust_decimal and rust_decimal_macros as dependencies.
  • BinaryOptionsToolsV2/Readme.md
    • Updated supported Python versions to 3.8 to 3.13.
    • Updated supported OS to include Windows, Linux, and macOS.
    • Revised the 'Available Features' list to reflect current capabilities and removed 'Temporarily Unavailable Features' section.
    • Updated installation commands for prebuilt wheels to version 0.2.6.
  • BinaryOptionsToolsV2/UNIMPLEMENTED.md
    • Added a new document outlining unimplemented features and placeholders in the core and Python extension modules.
  • BinaryOptionsToolsV2/pyproject.toml
    • Added python-source = "python" to [tool.maturin] configuration.
    • Updated Python version classifiers to include 3.13.
  • BinaryOptionsToolsV2/python/BinaryOptionsToolsV2/BinaryOptionsToolsV2.pyi
    • Added type hints for open_pending_order and create_raw_order_with_timeout_and_retry methods.
  • BinaryOptionsToolsV2/python/BinaryOptionsToolsV2/init.py
    • Refactored Python package initialization to correctly import Rust module attributes and submodules, handling potential import errors gracefully.
    • Updated __all__ to include new core attributes.
  • BinaryOptionsToolsV2/python/BinaryOptionsToolsV2/config.py
    • Increased connection_initialization_timeout_secs default from 30 to 60 seconds.
    • Added terminal_logging and log_level fields for logging configuration.
  • BinaryOptionsToolsV2/python/BinaryOptionsToolsV2/pocketoption/asynchronous.py
    • Simplified SSID sanitization logic.
    • Increased wait_for_assets default timeout from 30.0 to 60.0 seconds.
    • Changed __aexit__ context manager to call shutdown() instead of disconnect().
    • Added get_deal_end_time, get_pending_deals, open_pending_order, active_assets, send_raw_message, create_raw_order, create_raw_order_with_timeout, create_raw_order_with_timeout_and_retry, and create_raw_iterator methods.
    • Removed commented-out NotImplementedError for get_candles and get_candles_advanced.
  • BinaryOptionsToolsV2/python/BinaryOptionsToolsV2/pocketoption/synchronous.py
    • Removed __del__ method.
    • Changed __exit__ context manager to call shutdown() instead of disconnect().
    • Added close method for explicit client and event loop shutdown.
    • Added get_deal_end_time, get_pending_deals, open_pending_order, create_raw_iterator, send_raw_message, create_raw_order, create_raw_order_with_timeout, create_raw_order_with_timeout_and_retry, and active_assets methods.
  • BinaryOptionsToolsV2/python/BinaryOptionsToolsV2/tracing.py
    • Refactored import paths for RustLogger and LogBuilder to adapt to package structure changes.
    • Modified log_file and terminal methods in LogBuilder to return self for chaining.
  • BinaryOptionsToolsV2/python/BinaryOptionsToolsV2/validator.py
    • Refactored import paths for RawValidator to adapt to package structure changes.
    • Fixed regex pattern for regex static method to correctly escape backslashes.
  • BinaryOptionsToolsV2/src/error.rs
    • Updated error message for NotAllowed variant to include context.
  • BinaryOptionsToolsV2/src/framework.rs
    • Updated PyContext and PyVirtualMarket to use Decimal for financial amounts and prices, converting from f64 using f64_to_decimal.
  • BinaryOptionsToolsV2/src/logs.rs
    • Changed tracing_subscriber::registry().with(layers).init() to try_init() to prevent panics if already initialized.
  • BinaryOptionsToolsV2/src/pocketoption.rs
    • Updated connection timeouts in new and create constructors from 10 to 20 seconds.
    • Modified new_with_config and create_with_config to handle PocketOption::new_with_config directly without explicit timeout, relying on internal config.
    • Updated buy, sell, and balance methods to use Decimal for amounts and return values.
    • Added open_pending_order, get_pending_deals, active_assets, and shutdown methods.
  • BinaryOptionsToolsV2/src/validator.rs
    • Implemented Default trait for RawValidator.
    • Modified PyCustomValidator to use unwrap_or_default() for boolean extraction, ensuring graceful handling of non-boolean return values.
  • CHANGELOG.md
    • Updated version to 0.2.6.
    • Added changelog entries for 0.2.6 including robust SSID parsing, automated asset gathering, new wait_for_assets method, refactored GitHub templates, increased timeouts, improved WebSocket routing rules, updated documentation workflow, and fixed GitHub Pages 404 error and history retrieval race conditions.
  • ForLLMsAndAgents/guidelines.md
    • Added new file with code style, commit conventions, testing standards, and workflow guidelines.
  • ForLLMsAndAgents/product.md
    • Added new file providing product context, primary users, main goal, and key features of BinaryOptionsTools-v2.
  • ForLLMsAndAgents/tech-stack.md
    • Added new file detailing the tech stack used in BinaryOptionsTools-v2, including languages, frameworks, libraries, and tooling.
  • README.md
    • Updated Python version badge to include 3.13.
    • Updated Table of Contents heading.
    • Revised 'Key Highlights' and 'Features' sections to use bullet points and updated descriptions.
    • Updated installation commands for prebuilt wheels to version 0.2.6.
    • Updated API Reference link in the footer.
  • crates/binary_options_tools/Cargo.toml
    • Updated package version to 0.2.0.
    • Added ryu and rust_decimal_macros dependencies.
    • Enabled serde-with-float feature for rust_decimal.
  • crates/binary_options_tools/data/expert_options_regions.json
    • Added new ExpertOption finance-specific URLs and updated coordinates for existing regions.
  • crates/binary_options_tools/data/pocket_options_regions.json
    • Updated PocketOption region URLs and coordinates, simplifying the list to fewer, more relevant regions.
  • crates/binary_options_tools/src/config.rs
    • Increased default connection_initialization_timeout from 30 to 60 seconds.
  • crates/binary_options_tools/src/expertoptions/connect.rs
    • Changed info! logs to debug! for connection attempts.
    • Added a jittered delay between failed connection attempts to prevent rapid retries.
  • crates/binary_options_tools/src/expertoptions/modules/keep_alive.rs
    • Updated LightweightModule::new signature to include RunnerCommand sender.
  • crates/binary_options_tools/src/expertoptions/modules/profile.rs
    • Updated ApiModule::new signature to include RunnerCommand sender.
    • Modified run loop to use biased select and return Ok(()) on channel closure.
  • crates/binary_options_tools/src/expertoptions/types.rs
    • Changed Asset::symbol from String to Option<String>.
    • Added Asset::get_symbol method to retrieve the symbol or name if symbol is None.
    • Updated Assets::from to use get_symbol for map keys.
  • crates/binary_options_tools/src/framework/market.rs
    • Changed amount parameter type in buy and sell methods to Decimal.
    • Changed return type of balance method to Decimal.
  • crates/binary_options_tools/src/framework/virtual_market.rs
    • Updated VirtualMarket to use Decimal for balance, amount, and entry_price.
    • Modified new constructor to accept Decimal for initial_balance.
    • Updated update_price to accept Decimal for price.
    • Adjusted profit calculation to use Decimal arithmetic and dec! macro.
  • crates/binary_options_tools/src/lib.rs
    • Updated documentation comment to use ````text` block.
  • crates/binary_options_tools/src/pocketoption/candle.rs
    • Changed Candle::timestamp and BaseCandle::timestamp from f64 to i64.
    • Updated HistoryItem to handle serde_json::Value for timestamps and prices, converting to i64 and f64 respectively.
    • Adjusted compile_candles_from_ticks to work with i64 timestamps.
    • Corrected logic for SubscriptionType::time_aligned duration validation.
    • Updated SubscriptionType::update to use i64 timestamps for BaseCandle.
  • crates/binary_options_tools/src/pocketoption/connect.rs
    • Removed FuturesUnordered for connection attempts, now iterating sequentially with jittered delays.
    • Added FALLBACK_URLS constant for deterministic fallback if server list fetch fails.
    • Changed info! logs to debug! for connection attempts.
    • Improved disconnect method logging to indicate graceful shutdown sequence.
  • crates/binary_options_tools/src/pocketoption/error.rs
    • Changed FailOpenOrder::amount from f64 to Decimal.
  • crates/binary_options_tools/src/pocketoption/modules/assets.rs
    • Updated LightweightModule::new signature to include RunnerCommand sender.
    • Modified rule to use MultiPatternRule for updateAssets.
    • Added logic to parse 1-step Socket.IO messages for updateAssets.
  • crates/binary_options_tools/src/pocketoption/modules/balance.rs
    • Changed BalanceMessage::balance from f64 to Decimal.
    • Updated LightweightModule::new signature to include RunnerCommand sender.
    • Improved parsing of balance messages to handle both binary and 1-step text Socket.IO messages.
  • crates/binary_options_tools/src/pocketoption/modules/deals.rs
    • Changed CloseOrder::_profit from f64 to Decimal.
    • Updated ApiModule::new signature to include RunnerCommand sender.
    • Refactored run loop to use biased select and improved message parsing to handle 1-step text Socket.IO messages and binary placeholders for deal updates.
    • Added process_text_data helper function for consistent text message processing.
  • crates/binary_options_tools/src/pocketoption/modules/get_candles.rs
    • Updated CandleData::time from i64 to i64 (no change, but context for Candle::timestamp update).
    • Modified Candle::try_from to use f64_to_decimal for open, high, low, close fields and i64 for timestamp.
    • Updated ApiModule::new signature to include RunnerCommand sender.
    • Improved parsing of LoadHistoryPeriodResult to handle 1-step text Socket.IO messages.
  • crates/binary_options_tools/src/pocketoption/modules/historical_data.rs
    • Increased HISTORICAL_DATA_TIMEOUT from 10 to 30 seconds.
    • Changed CommandResponse::Ticks timestamps from f64 to i64.
    • Updated HistoryResponse::timestamps from u64 to f64 (then cast to i64 during processing).
    • Updated ApiModule::new signature to include RunnerCommand sender.
    • Improved message parsing in run to handle 1-step Socket.IO messages and binary placeholders for historical data responses.
  • crates/binary_options_tools/src/pocketoption/modules/keep_alive.rs
    • Updated LightweightModule::new signature to include RunnerCommand sender.
    • Refactored InitModule::run to use RunnerCommand::Shutdown on authentication failure (server sent 41 disconnect).
    • Improved InitModule message processing to handle binary parts of successauth and explicitly send initialization messages.
    • Introduced InitRule struct to manage state for two-step authentication messages.
  • crates/binary_options_tools/src/pocketoption/modules/pending_trades.rs
    • Increased PENDING_ORDER_TIMEOUT from 10 to 30 seconds.
    • Changed Command::OpenPendingOrder::amount and open_price from f64 to Decimal.
    • Updated ApiModule::new signature to include RunnerCommand sender.
    • Improved message parsing in run to handle 1-step text Socket.IO messages for pending order responses.
  • crates/binary_options_tools/src/pocketoption/modules/raw.rs
    • Updated ApiModule::new signature to include RunnerCommand sender.
    • Added #[allow(clippy::type_complexity)] to sinks field.
  • crates/binary_options_tools/src/pocketoption/modules/server_time.rs
    • Updated LightweightModule::new signature to include RunnerCommand sender.
    • Improved message parsing in run to handle both binary and text messages for server time updates.
  • crates/binary_options_tools/src/pocketoption/modules/subscriptions.rs
    • Updated ApiModule::new signature to include RunnerCommand sender.
    • Introduced ResponseRouter to manage command responses, allowing SubscriptionsHandle to wait for specific responses.
    • Changed SubscriptionEvent::price to Decimal and timestamp to i64.
    • Updated SubscriptionStream and SubscriptionsHandle to use the ResponseRouter for waiting on command responses.
    • Modified Command::SubscriptionCount to include command_id.
    • Updated CommandResponse::SubscriptionCount to include command_id and count.
  • crates/binary_options_tools/src/pocketoption/modules/trades.rs
    • Changed Command::OpenOrder::amount from f64 to Decimal.
    • Changed PendingOrderTracker::amount from f64 to Decimal.
    • Changed failure_matching HashMap key for amount from String to Decimal.
    • Updated ApiModule::new signature to include RunnerCommand sender.
    • Improved message parsing in run to handle 1-step text Socket.IO messages for trade responses.
  • crates/binary_options_tools/src/pocketoption/pocket_client.rs
    • Changed MINIMUM_TRADE_AMOUNT and MAXIMUM_TRADE_AMOUNT from f64 to Decimal.
    • Updated Market trait implementation to use Decimal for amounts and balance.
    • Modified new_with_config to prioritize current_url from SSID over config URLs.
    • Updated balance method to return Decimal.
    • Modified trade, buy, and sell methods to accept Decimal amounts.
    • Added active_assets method to retrieve only active assets.
    • Improved wait_for_assets error message to include account type and balance status.
    • Added shutdown_ref (commands shutdown without consuming client) and shutdown_owned (consumes client) methods.
  • crates/binary_options_tools/src/pocketoption/regions.rs
    • Modified sort_servers to sort distances in ascending order.
    • Added get_server_for_ip and get_servers_for_ip methods for IP-based server lookup.
  • crates/binary_options_tools/src/pocketoption/ssid.rs
    • Added raw and json_raw fields to Demo and Real structs to store original SSID strings.
    • Added session_raw field to Real struct.
    • Redacted sensitive information in Debug implementation for Real struct.
    • Improved Ssid::parse to store original raw and JSON strings.
    • Added current_url method to extract current URL from SSID data.
    • Added session_id method to retrieve the session ID string.
    • Updated user_agent string for Demo accounts.
    • Modified server and servers methods to use IP-based server lookup via get_server_for_ip and get_servers_for_ip.
  • crates/binary_options_tools/src/pocketoption/state.rs
    • Changed balance field from RwLock<Option<f64>> to RwLock<Option<Decimal>>.
    • Updated set_balance and get_balance methods to use Decimal.
    • Changed get_server_time, update_server_time, local_to_server, and server_to_local timestamps from f64 to i64.
    • Updated TradeState::recent_trades key to use Decimal for amount.
  • crates/binary_options_tools/src/pocketoption/types.rs
    • Changed ServerTime::last_server_time from f64 to i64.
    • Updated ServerTime::update, local_to_server, server_to_local, and get_server_time methods to use i64 timestamps.
    • Changed StreamData::price from f64 to Decimal and timestamp from f64 to i64.
    • Updated StreamData deserialization to correctly parse Decimal prices and i64 timestamps.
    • Modified MultiPatternRule to handle binary placeholders and 1-step Socket.IO messages more robustly.
    • Made CandleLength and Asset structs Serialize.
    • Changed FailOpenOrder::amount from f64 to Decimal.
    • Updated OpenOrder::amount to use Decimal with rust_decimal::serde::float.
    • Changed Deal fields (amount, profit, open_price, close_price, amount_usd, amount_usd2) to Decimal and timestamps to use unix_timestamp module.
    • Changed PendingOrder::amount and open_price to Decimal.
    • Changed OpenPendingOrder::amount and open_price to Decimal.
    • Changed SubscriptionEvent::price to Decimal and timestamp to i64.
  • crates/binary_options_tools/src/pocketoption/utils.rs
    • Updated get_index to use Utc::now().timestamp() for time component.
    • Refactored get_user_location and get_public_ip to use multiple IP providers and reqwest::Client with a timeout for increased reliability.
    • Introduced CONNECTOR as a OnceLock to initialize rustls connector once.
    • Added connection timeout to try_connect function.
    • Replaced float_time module with unix_timestamp module for serializing/deserializing DateTime<Utc> to i64 Unix timestamps.
  • crates/binary_options_tools/src/utils/mod.rs
    • Added f64_to_decimal utility function for precise f64 to Decimal conversion.
    • Changed print_handler logging level from info! to debug!.
  • crates/binary_options_tools/src/validator.rs
    • Implemented Default for RawValidator.
    • Modified PyCustomValidator::call to use unwrap_or_default() for boolean extraction, preventing panics on invalid Python callable return values.
  • crates/core-pre/Cargo.toml
    • Updated package version to 0.2.0.
  • crates/core-pre/examples/echo_client.rs
    • Updated ApiModule::new signature to include RunnerCommand sender.
    • Modified run loop to explicitly check Message::Text before processing.
  • crates/core-pre/examples/middleware_example.rs
    • Updated ApiModule::new signature to include RunnerCommand sender.
  • crates/core-pre/examples/testing_echo_client.rs
    • Updated ApiModule::new signature to include RunnerCommand sender.
    • Modified run loop to explicitly check Message::Text before processing.
  • crates/core-pre/src/builder.rs
    • Updated LightweightModule and ApiModule factories to pass RunnerCommand sender to modules.
  • crates/core-pre/src/client.rs
    • Added shutdown_ref method to send a shutdown command to the runner without consuming the client.
    • Changed info! logs to debug! for connection successful and reconnect callbacks.
    • Implemented logic to reset reconnect attempts after a stable connection period (10 seconds).
  • crates/core-pre/src/traits.rs
    • Introduced RunnerCommand enum for controlling the client runner.
    • Updated ApiModule::new and LightweightModule::new signatures to include runner_command_tx: AsyncSender<RunnerCommand>.
  • crates/core-pre/src/utils/tracing.rs
    • Fixed typo in stream_logs_layer parameter name from timout to timeout.
  • crates/core-pre/tests/testing_wrapper_tests.rs
    • Updated ApiModule::new signature to include RunnerCommand sender.
  • crates/core/Cargo.toml
    • Updated package version to 0.2.0.
    • Updated binary-options-tools-macros dependency to version 0.2.0.
  • scripts/bot-cli.py
    • Moved from SortLaterOr_rm/bot-cli.py.
  • scripts/modify_subs.py
    • Moved from SortLaterOr_rm/modify_subs.py.
  • tests/python/core/test_basic.py
    • Moved from tests/python/test_basic.py.
  • tests/python/experimental/reproduce_race.py
    • Moved from tests/python/reproduce_race.py.
  • tests/python/experimental/test.py
    • Moved from tests/python/test.py.
  • tests/python/pocketoption/test_assets.py
    • Moved from tests/python/test_assets.py.
  • tests/python/pocketoption/test_integration.py
    • Moved from tests/python/test_all.py.
Ignored Files
  • Ignored by pattern: .github/workflows/** (2)
    • .github/workflows/CI.yml
    • .github/workflows/docs.yml
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@sixtysixx sixtysixx self-assigned this Feb 13, 2026
@sixtysixx sixtysixx requested review from Rick-29 and theshadow76 and removed request for Rick-29 February 13, 2026 09:22
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a significant revamp, including a major refactoring to use Decimal for financial values, which greatly improves precision. The connection logic, module architecture, and project documentation have also been substantially improved for better robustness and maintainability. While these are excellent changes, I've identified several critical issues where Decimal to f64 conversions are handled unsafely, potentially leading to silent data corruption. I've also noted a couple of minor documentation errors that could mislead users.

sixtysixx and others added 3 commits February 13, 2026 02:27
…synchronous.py

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (9)
BinaryOptionsToolsUni/README.md (1)

168-182: ⚠️ Potential issue | 🟡 Minor

Section heading should be ### C#, not ### C.

The code block contains C# (.NET) code (using BinaryOptionsToolsUni;, await, Console.WriteLine, etc.), but the heading reads "C" instead of "C#". This is inconsistent with the rest of the README where each language section heading matches its content.

📝 Proposed fix
-### C
+### C#
BinaryOptionsToolsV2/python/BinaryOptionsToolsV2/BinaryOptionsToolsV2.pyi (1)

155-155: ⚠️ Potential issue | 🟠 Major

Update start_tracing stub to match the 4-parameter Rust signature.

The stub declares start_tracing(level: str = "info") -> None, but the Rust implementation (logs.rs) defines it with 4 required parameters: path: String, level: String, terminal: bool and a layers argument. The Python code at tracing.py:157 correctly calls it with 4 arguments, so the stub will cause a TypeError at runtime. Update the stub to:

def start_tracing(path: str, level: str, terminal: bool, layers: list = None) -> None: ...
BinaryOptionsToolsV2/python/BinaryOptionsToolsV2/pocketoption/asynchronous.py (1)

503-525: ⚠️ Potential issue | 🔴 Critical

Bug: payout() returns None when called without an asset argument.

When asset is None (the default), neither the if isinstance(asset, str) nor elif isinstance(asset, list) branch executes, so the function falls through and implicitly returns None. The full payout dictionary is never returned, breaking the primary use case shown in the examples where full_payout = await api.payout() expects a dictionary. A return payout statement is missing at the end of the method.

Proposed fix
     payout = json.loads(await self.client.payout())
     if isinstance(asset, str):
         return payout.get(asset)
     elif isinstance(asset, list):
         return [payout.get(ast) for ast in asset]
+    return payout
crates/binary_options_tools/src/pocketoption/ssid.rs (1)

134-152: ⚠️ Potential issue | 🟡 Minor

Verify that recursive Self::parse terminates for all inputs.

Line 141: If the trimmed input is a valid JSON string, it's unquoted and parse is called recursively. This is safe because serde_json::from_str::<String> only succeeds for a JSON-encoded string literal (e.g., "\"inner\"""inner"), and each recursion peels one layer of encoding. However, a pathologically crafted input with many nesting levels could cause deep recursion (stack overflow).

Consider adding a depth limit or converting to an iterative loop if externally-supplied SSIDs are accepted.

crates/binary_options_tools/src/pocketoption/modules/subscriptions.rs (1)

828-838: ⚠️ Potential issue | 🟠 Major

Cloning SubscriptionStream shares the MPSC receiver, causing messages to be split between clones.

kanal::AsyncReceiver is an MPSC (bounded) channel receiver. Cloning it creates multiple consumers that compete for messages from the same channel, resulting in each message being received by only one clone. The receive() method expects sequential consumption of all messages, so cloned instances will silently miss data.

Additionally, each cloned instance has its own Drop implementation that sends an Unsubscribe command. This means dropping one clone unsubscribes the stream for all other clones, breaking them unexpectedly.

If Clone is needed for framework integration, use a broadcast channel instead, or document the intentional single-consumer-per-clone design and prevent accidental cloning through the type system.

crates/binary_options_tools/src/expertoptions/modules/profile.rs (2)

228-228: ⚠️ Potential issue | 🟠 Major

Remove println!("Here") debug artifact from production code.

This will emit unstructured output to stdout on every module start. Replace with a debug! call or remove entirely.

Proposed fix
-        println!("Here");
+        debug!(target: "ProfileModule", "Starting profile module run loop.");

325-343: ⚠️ Potential issue | 🟠 Major

Remove dbg!() macros from send_startup_messages.

Lines 330–331 use dbg!() which writes to stderr and is not suitable for production. Replace with structured tracing.

Proposed fix
-        if dbg!(self.state.is_demo().await) {
-            dbg!("Sent demo message");
+        if self.state.is_demo().await {
+            debug!(target: "ProfileModule", "Sending demo context message.");
crates/core-pre/src/client.rs (1)

505-508: ⚠️ Potential issue | 🟡 Minor

RunnerCommand::Connect and RunnerCommand::Reconnect silently discarded during active session.

As noted in the traits.rs review, the _ => {} catch-all on line 507 means Reconnect commands sent via client.reconnect() during an active session are consumed and ignored without feedback. At minimum, add a debug! log so this isn't invisible.

Proposed fix
-                            _ => {}
+                            other => {
+                                debug!(target: "Runner", "Ignoring command {other:?} during active session.");
+                            }
crates/binary_options_tools/src/lib.rs (1)

39-39: ⚠️ Potential issue | 🟡 Minor

Type has a spelling error: RecieverStream should be ReceiverStream.

The misspelling originates in the source crate definition (crates/core-pre/src/utils/stream.rs and mirrored in crates/core/src/general/stream.rs), and since it's re-exported here in the public API, downstream users see the misspelled name. Fixing this requires renaming the type definition and updating all ~30+ usages across the codebase.

🤖 Fix all issues with AI agents
In `@crates/binary_options_tools/src/framework/virtual_market.rs`:
- Around line 309-310: The computed profit currently assigns total payout to the
local variable profit (when win is true); change the calculation so Deal.profit
stores net gain/loss: set profit to trade.amount *
Decimal::from(trade.payout_percent) / dec!(100.0) when win, dec!(0.0) when draw,
and -trade.amount when loss. Update the branch using the variables win, draw,
trade.amount and trade.payout_percent (the profit local and the Deal.profit
assignment) to implement those three cases so the stored profit matches
PocketOption API semantics.

In `@crates/binary_options_tools/src/pocketoption/modules/subscriptions.rs`:
- Around line 36-66: The background router (ResponseRouter) can drop responses
if a module replies before wait_for inserts the oneshot sender; implement a
pre-registration API on ResponseRouter (e.g., async fn register(&self, id: Uuid)
-> oneshot::Receiver<CommandResponse>) that locks pending, inserts a new
oneshot::Sender for id and returns the Receiver, and update callers (places that
currently call wait_for(id) after sending Command like subscribe()) to call
router.register(id) before sending the Command to the module, then await the
returned receiver and map the oneshot error to PocketError as wait_for does;
keep ResponseRouter::new and the background task unchanged except that it will
now find pre-registered senders in pending.

In `@crates/core/data/client_enhanced.rs`:
- Around line 485-488: BinaryOptionsToolsError::WebsocketConnectionError is
being constructed with a bare
tokio_tungstenite::tungstenite::Error::ConnectionClosed but the variant expects
a boxed error; update the constructor to wrap the tungstenite error in
Box::new(...) (e.g.,
Box::new(tokio_tungstenite::tungstenite::Error::ConnectionClosed)) wherever
BinaryOptionsToolsError::WebsocketConnectionError is created (including the
instance in client_enhanced.rs and the similar occurrence in connection.rs) so
the variant signature matches.
- Around line 388-440: The init() function currently accepts handler: Handler
and credentials: Creds but does not store them on EnhancedWebSocketInner and the
stored data: Data is never used in the receiver task; update the struct to
include fields for credentials and handler (e.g., credentials: Creds, handler:
Handler) and set them in init(), then modify the message receiver/receiver task
(the code that consumes message_receiver and emits events) to
authenticate/connect using credentials (during connect/reconnect logic) and to
pass incoming messages into the Data/Handler pipeline (use the Data<T,Transfer>
instance and call the appropriate handler methods to process messages before
emitting events). Ensure references to EnhancedWebSocketInner, init,
message_receiver, data, Handler, and Creds are updated everywhere the
receiver/connect logic runs so the handler and credentials are actually used.

In `@crates/core/data/client2.rs`:
- Around line 537-543: The closure parameter type in update_websocket_config is
wrong: update_websocket_config currently declares F: FnOnce(&mut
WebSocketConfig) but delegates to self.shared_state.update_config which expects
FnOnce(&mut WebSocketClientConfig); change update_websocket_config to accept F:
FnOnce(&mut WebSocketClientConfig) (or alternatively change
shared_state.update_config to use WebSocketConfig if you intend to unify types),
update the function signature of update_websocket_config and any callers to use
WebSocketClientConfig, and ensure imports/usages of WebSocketConfig vs
WebSocketClientConfig are reconciled so the types match when calling
shared_state.update_config.
- Around line 1-1003: The file fails to compile due to multiple API mismatches:
align the WebSocketEvent definition with its usage (make variants use the same
tuple/struct shapes or update usage sites like broadcast_event and handler
matches to the defined variants), unify the trait types by replacing/renaming
WebSocketEventHandler or EventHandler so the stored handlers
(SharedState.event_handlers) and implementations (LoggingEventHandler,
StatsEventHandler) implement the same trait used throughout (ensure the
async_trait signature matches handle_event(&self, event: &WebSocketEvent<...>)
or EventHandler::handle_event(event)), fix SharedState fields and constructors
by adding or renaming the missing stats field or updating calls to
get_stats/update_stats to use connection_state (update SharedState::new
signature or its callers so arity matches), remove the unsupported
SplitSink::clone() call in run_connection and instead move or wrap the sink into
an Arc<Mutex<...>> or spawn the sender task using the original write (transfer
ownership properly), pick one config type (WebSocketConfig vs
WebSocketClientConfig) and make get_config()/update_config()/usage consistent,
and make LoggingEventHandler’s match over WebSocketEvent exhaustive (add missing
arms or a catch-all _ arm). Apply these changes to the referenced symbols:
WebSocketEvent, WebSocketEventHandler/EventHandler, SharedState::new,
get_stats/update_stats and connection_state, run_connection (sender_task
creation and write usage), WebSocketConfig/WebSocketClientConfig and
LoggingEventHandler::handle_event.
- Around line 70-83: The code defines a new WebSocketEventHandler trait that
conflicts with the existing EventHandler trait used elsewhere
(LoggingEventHandler, StatsEventHandler, TestEventHandler), causing a type
mismatch; remove/stop using WebSocketEventHandler and unify on the existing
EventHandler trait: delete or stop referencing WebSocketEventHandler, change
SharedState::event_handlers to store Arc<dyn EventHandler<Transfer>>, update
WebSocketClient2::add_event_handler to accept Arc<dyn EventHandler<Transfer>>
and ensure the call sites and method signatures (handle_event in the handlers
and any invocations inside WebSocketClient2 that pass events) match
EventHandler’s expected signature (convert any &WebSocketEvent<...> usages to
the owned/event form EventHandler expects). Ensure imports reference
crate::general::events::EventHandler and that all handler structs implement that
trait.
- Around line 406-418: The methods get_stats and update_stats reference a
non-existent self.stats; change them to operate on the existing connection_state
field or add a new stats field—pick one: (A) Prefer changing the methods to use
connection_state: in get_stats() read and clone from self.connection_state
(return the appropriate type, e.g., ConnectionState or convert to
ConnectionStats if callers require it) and in update_stats<F> take FnOnce(&mut
ConnectionState) and apply it to self.connection_state.write().await; or (B) if
you truly need a separate ConnectionStats storage, add stats:
Arc<RwLock<ConnectionStats>> to SharedState and initialize it where SharedState
is constructed, then keep get_stats/update_stats operating on self.stats.
Reference the methods get_stats, update_stats and the field connection_state
when making the change and update all call sites (start_event_loop,
run_connection) to match the chosen type (ConnectionState vs ConnectionStats).
- Around line 738-765: The sender task currently calls write.clone() but
SplitSink (the write variable) is not Clone; instead move the write into the
spawned task and stop attempting to clone it: change the closure capture to take
ownership of write (e.g., remove write.clone() and move write into the async
move) and adjust the surrounding run_connection signature so write is not
declared as a mutable top-level borrow that must be cloned; ensure the spawned
task owns a mutable write (SplitSink<WebSocketStream<MaybeTlsStream<TcpStream>>,
Message>) and use that owned write inside the loop that reads from
message_receiver and updates shared_state (referencing sender_task, write,
run_connection, message_receiver, shared_state).
- Around line 566-568: SharedState::new is being invoked with a single argument
but its signature requires two (data, buffer_size); update the call site in
client2.rs where SharedState::new(data) is used to pass a proper buffer_size
(for example: use the buffer size value from the existing config object or a
sensible default constant) so the call matches SharedState::new(data,
buffer_size) and compiles.
- Around line 35-68: The enum WebSocketEvent is defined with struct-style
variants but is used throughout as tuple/unit variants (e.g.,
WebSocketEvent::Connected, WebSocketEvent::Disconnected(reason),
WebSocketEvent::Error(e.to_string())), which causes compilation errors; update
the WebSocketEvent declaration to use tuple and unit variants to match usage
(e.g., Connected(Option<String>) or Connected, Disconnected(String),
Error(String, Option<String>), MessageReceived(Transfer),
RawMessageReceived(Transfer::Raw), MessageSent(Transfer), PingSent(Instant),
PongReceived(Instant), etc.) so all construction and match sites across the file
compile without changing those sites.
- Around line 913-935: The match in LoggingEventHandler::handle_event over
WebSocketEvent is non‑exhaustive and misses variants (Authenticated,
BalanceUpdated, OrderOpened, OrderClosed, StreamUpdate, CandlesReceived,
PingSent, PongReceived); update the match in the handle_event method to either
add a catch‑all arm (e.g., _ => debug!("Unhandled WebSocket event: {:?}",
event)) or explicitly handle the missing variants with appropriate log calls,
ensuring WebSocketEvent is fully covered so the code compiles.

In `@README.md`:
- Around line 114-128: The README contains installation URLs hard-coded to
release version "0.2.6" which does not exist; update the three wheel URLs (the
Windows, Linux, and macOS pip install links shown) to point to the actual
published release "0.2.1" (replace every occurrence of "0.2.6" in those URLs and
filenames with "0.2.1"), or alternatively revert the lines to a generic pip
install command that references the package name without a release-specific
wheel if you prefer to wait for 0.2.6.
🟠 Major comments (14)
crates/core/data/batching.rs-145-151 (1)

145-151: ⚠️ Potential issue | 🟠 Major

Handle rate == 0 to prevent an infinite wait.

With rate == 0, acquire() will sleep forever. Either reject zero in new() or treat it as “no limit.”

💡 Example safeguard
 pub fn new(rate: u32) -> Self {
-    Self {
+    assert!(rate > 0, "rate must be > 0");
+    Self {
         rate,
         tokens: Arc::new(Mutex::new(rate)),
         last_refill: Arc::new(Mutex::new(Instant::now())),
     }
 }

Also applies to: 153-176

crates/core/data/batching.rs-44-46 (1)

44-46: ⚠️ Potential issue | 🟠 Major

Guard against zero/invalid batching config to prevent panics and unexpected stalls.

batch_size == 0 causes division by zero, batch_timeout == Duration::ZERO will panic in interval() (tokio asserts period must be non-zero), and max_pending < batch_size creates a zero-capacity channel that can unexpectedly block add_message. Add validation when constructing the batcher to enforce these invariants.

Also applies to lines 109–116 (background flusher interval creation).

💡 Example validation approach (keeps API stable)
 pub fn new(config: BatchingConfig) -> Self {
-    let (batch_sender, batch_receiver) = bounded(config.max_pending / config.batch_size);
+    assert!(config.batch_size > 0, "batch_size must be > 0");
+    assert!(!config.batch_timeout.is_zero(), "batch_timeout must be > 0");
+    let capacity = (config.max_pending / config.batch_size).max(1);
+    let (batch_sender, batch_receiver) = bounded(capacity);
crates/binary_options_tools/src/pocketoption/modules/keep_alive.rs-110-130 (1)

110-130: ⚠️ Potential issue | 🟠 Major

Logging the user's public IP raises a privacy/compliance concern.

Line 115 calls get_public_ip().await and logs the user's public IP address at warn level on auth rejection. This could conflict with GDPR/CCPA requirements if logs are retained or shipped to a centralized system. Additionally, get_public_ip() makes an external network call in the error path, adding latency before the shutdown signal is sent.

Consider removing the IP log or gating it behind a debug/trace level so it doesn't appear in production logs by default.

crates/core/data/client2.rs-326-330 (1)

326-330: ⚠️ Potential issue | 🟠 Major

Handlers beyond max_concurrent_handlers are silently skipped, not deferred.

When the number of matching handlers exceeds max_concurrent_handlers, the loop breaks (line 328), permanently skipping the remaining handlers for this event with no warning logged. This means later-registered handlers may silently never fire.

Consider using a tokio::sync::Semaphore to limit concurrency while still eventually invoking all handlers, or at minimum log a warning when handlers are being dropped.

crates/core/data/client2.rs-269-342 (1)

269-342: 🛠️ Refactor suggestion | 🟠 Major

Two impl SharedState blocks with duplicate/conflicting methods.

There are two impl blocks for SharedState<T>:

  • Block 1 (lines 269–342): add_event_handler / remove_event_handler / get_connection_state / update_connection_state / broadcast_event
  • Block 2 (lines 380–433): new / add_handler / remove_handler / get_stats / update_stats / get_config / update_config

add_event_handleradd_handler, and remove_event_handlerremove_handler — these are near-identical duplicates. Additionally, get_stats/update_stats reference a non-existent self.stats field while get_connection_state/update_connection_state use the actual self.connection_state field. Consolidate into a single impl block with one set of methods.

Also applies to: 380-433

crates/core/data/client2.rs-352-378 (1)

352-378: 🛠️ Refactor suggestion | 🟠 Major

WebSocketConfig is a subset of WebSocketClientConfig — consolidate.

WebSocketConfig duplicates 6 of the 13 fields from WebSocketClientConfig. This duplication directly caused the type mismatch bug above and will continue to be a maintenance footgun. Consider removing WebSocketConfig and using WebSocketClientConfig throughout, or embedding WebSocketConfig as a field within WebSocketClientConfig.

crates/core/data/client2.rs-866-877 (1)

866-877: ⚠️ Potential issue | 🟠 Major

Dropping the losing JoinHandle in select! leaks the task.

In Tokio, dropping a JoinHandle does not abort the underlying task — it continues running detached. When one branch of this select! completes, the other task will keep running indefinitely.

Abort the remaining task explicitly:

Proposed fix
-        tokio::select! {
-            result = sender_task => {
-                result??;
-            }
-            result = receiver_task => {
-                result??;
-            }
-        }
+        tokio::select! {
+            result = sender_task => {
+                receiver_task.abort();
+                result??;
+            }
+            result = receiver_task => {
+                sender_task.abort();
+                result??;
+            }
+        }
crates/core/data/client2.rs-304-341 (1)

304-341: ⚠️ Potential issue | 🟠 Major

Read lock on event_handlers held across join_all await — can block writes for up to 5 seconds.

The handlers read guard (line 305) is held while spawning tasks and awaiting join_all (line 337), which has a 5-second timeout. Any concurrent call to add_handler/remove_handler will be blocked for the entire duration, and nested handler calls that attempt to modify the registry will deadlock.

Clone the handler list into a local Vec and drop the lock before spawning:

Proposed fix
     pub async fn broadcast_event(&self, event: WebSocketEvent<T::Transfer>) {
-        let handlers = self.event_handlers.read().await;
         let config = self.get_config().await;
+        let handlers: Vec<_> = {
+            let guard = self.event_handlers.read().await;
+            guard.clone()
+        };
 
         if handlers.is_empty() {
             return;
         }
BinaryOptionsToolsV2/python/BinaryOptionsToolsV2/tracing.py-155-157 (1)

155-157: ⚠️ Potential issue | 🟠 Major

Update .pyi stub to match the Rust function signature.

The .pyi file at line 155 declares def start_tracing(level: str = "info") -> None, but the actual Rust implementation takes 4 parameters: path, level, terminal, and layers. The call at line 157 passes all 4 arguments correctly, matching the Rust signature. Update the stub to:

def start_tracing(path: str, level: str, terminal: bool, layers: List[StreamLogsLayer]) -> None: ...
crates/core/data/client_enhanced.rs-856-891 (1)

856-891: ⚠️ Potential issue | 🟠 Major

Potential deadlock: write lock on connection_state held across event_manager.emit().

The state write guard (line 876) is held until the end of the function. The subsequent emit() call (line 882) invokes registered event handlers synchronously. If any handler attempts to read connection_state (e.g., via is_connected() or get_connection_stats()), it will deadlock because tokio's RwLock is not reentrant.

Drop the guard before emitting:

Proposed fix
     pub async fn disconnect(&self) -> BinaryOptionsResult<()> {
         info!("Disconnecting WebSocket client...");
 
         // Stop keep-alive manager
         if let Some(keep_alive_manager) = self.keep_alive.lock().await.as_mut() {
             keep_alive_manager.stop().await;
         }
 
         // Stop reconnection supervisor
         if let Some(task) = self.reconnect_task.lock().await.take() {
             task.abort();
         }
 
         // Cancel all background tasks
         let mut tasks = self.background_tasks.lock().await;
         for task in tasks.drain(..) {
             task.abort();
         }
+        drop(tasks);
 
         // Update connection state
-        let mut state = self.connection_state.write().await;
-        state.is_connected = false;
-        state.connection_start_time = None;
-        state.current_region = None;
+        {
+            let mut state = self.connection_state.write().await;
+            state.is_connected = false;
+            state.connection_start_time = None;
+            state.current_region = None;
+        }
 
         // Emit disconnected event
         self.event_manager
             .emit(Event::new(
                 EventType::Disconnected,
                 serde_json::json!({"reason": "manual_disconnect"}),
             ))
             .await?;
 
         info!("WebSocket client disconnected successfully");
         Ok(())
     }
crates/binary_options_tools/src/pocketoption/utils.rs-99-99 (1)

99-99: ⚠️ Potential issue | 🟠 Major

Full IP address logged at warn level — PII exposure risk.

Line 99 logs the unredacted ip_address parameter at warn level. Unlike the debug-level redacted logging in ssid.rs, this warn message will appear in production logs by default. Consider redacting the IP here as well.

🛡️ Proposed fix — redact IP in warning
-    tracing::warn!(target: "PocketUtils", "All geo providers failed for IP {}. Using fallback location.", ip_address);
+    tracing::warn!(target: "PocketUtils", "All geo providers failed for IP lookup. Using fallback location.");
crates/binary_options_tools/src/pocketoption/candle.rs-472-474 (1)

472-474: ⚠️ Potential issue | 🟠 Major

Panic on zero-duration: % by zero if duration is Duration::ZERO.

duration.as_secs() returns 0 for a zero duration, causing 86400 % 0 to panic at runtime. Add a guard before the modulo check.

🐛 Proposed fix
     pub fn time_aligned(duration: Duration) -> PocketResult<Self> {
+        if duration.as_secs() == 0 {
+            return Err(PocketError::General(
+                "Duration must be greater than zero for time-aligned subscription".to_string(),
+            ));
+        }
         if 24 * 60 * 60 % duration.as_secs() != 0 {
crates/binary_options_tools/src/pocketoption/modules/get_candles.rs-173-196 (1)

173-196: ⚠️ Potential issue | 🟠 Major

get_candles_advanced has no timeout and no mismatch retry limit.

Unlike historical_data.rs which has HISTORICAL_DATA_TIMEOUT and MAX_MISMATCH_RETRIES, this method loops forever awaiting a matching response. If the server never responds or keeps sending mismatched responses, the caller blocks indefinitely.

Suggested fix: add timeout and mismatch guard
     pub async fn get_candles_advanced(
         &self,
         asset: impl ToString,
         period: i64,
         time: i64,
         offset: i64,
     ) -> PocketResult<Vec<Candle>> {
         info!(target: "GetCandlesHandle", "Requesting candles for asset: {}, period: {}, time: {}, offset: {}", asset.to_string(), period, time, offset);
         let req_id = Uuid::new_v4();
 
         self.sender
             .send(Command::GetCandles {
                 asset: asset.to_string(),
                 period,
                 time,
                 offset,
                 req_id,
             })
             .await
             .map_err(CoreError::from)?;
 
+        let timeout_duration = std::time::Duration::from_secs(30);
+        let mut mismatch_count = 0;
+        const MAX_MISMATCHES: usize = 5;
         loop {
-            match self.receiver.recv().await {
-                Ok(CommandResponse::CandlesResult {
+            match tokio::time::timeout(timeout_duration, self.receiver.recv()).await {
+                Ok(Ok(CommandResponse::CandlesResult {
                     req_id: response_id,
                     candles,
-                }) => {
+                })) => {
                     if req_id == response_id {
                         return Ok(candles);
                     }
-                    // Continue waiting for the correct response
+                    mismatch_count += 1;
+                    if mismatch_count >= MAX_MISMATCHES {
+                        return Err(PocketError::General("Exceeded mismatch retries for get_candles".into()));
+                    }
                 }
                 // ... similar for Error variant
+                Err(_) => return Err(PocketError::Timeout {
+                    task: "get_candles".to_string(),
+                    context: format!("period: {}, offset: {}", period, offset),
+                    duration: timeout_duration,
+                }),
crates/core-pre/src/traits.rs-9-15 (1)

9-15: ⚠️ Potential issue | 🟠 Major

RunnerCommand::Reconnect sent by reconnect() is silently discarded.

The public reconnect() method (line 214) sends RunnerCommand::Reconnect, but in the main select! loop (lines 456–507 in client.rs), only Disconnect and Shutdown variants are handled explicitly. The Reconnect and Connect variants fall through to the catch-all _ => {} arm, meaning the command is consumed without effect.

The actual reconnection logic exists separately in the outer connection loop (lines 309–350), controlled by the is_hard_disconnect flag. A user calling client.reconnect() will not trigger an immediate reconnection attempt; the method silently succeeds but the sent command is ignored.

Either handle RunnerCommand::Reconnect and Connect in the match statement, or clarify the API contract to prevent users from expecting immediate reconnection.

🟡 Minor comments (26)
BinaryOptionsToolsV2/src/error.rs-15-15 (1)

15-15: ⚠️ Potential issue | 🟡 Minor

Typo: "descerializing" → "deserializing"

-    #[error("Error descerializing data, {0}")]
+    #[error("Error deserializing data, {0}")]
BinaryOptionsToolsV2/pyproject.toml-55-57 (1)

55-57: ⚠️ Potential issue | 🟡 Minor

Relative testpaths is fragile across different working directories.

testpaths = ["../tests"] resolves relative to the current working directory at pytest invocation, not relative to pyproject.toml. While this works when pytest is run from BinaryOptionsToolsV2/, it will fail if invoked from the repo root or other directories. CI avoids this issue by explicitly passing the test path (pytest ../../tests) as a command-line argument.

To make this more robust, either:

  • Use a path relative to the pyproject.toml location: testpaths = ["tests"] with a conftest.py at the repo root, or
  • Document the expected invocation directory clearly in CI configuration and project documentation.
ForLLMsAndAgents/guidelines.md-14-17 (1)

14-17: ⚠️ Potential issue | 🟡 Minor

Document the intentional deviation from PEP 8 line length.

Line 15 specifies a 120-character maximum, which deviates from PEP 8's recommended 79 characters (with flexibility up to 99 for some cases). While this is a valid project-specific choice, consider adding a brief note acknowledging this deviation to avoid confusion for contributors expecting strict PEP 8 compliance.

📝 Suggested clarification
 - **Formatting**: Follow [PEP 8](https://www.python.org/dev/peps/pep-0008/).
-- **Line Length**: Maximum of 120 characters (enforced by `ruff`).
+- **Line Length**: Maximum of 120 characters (enforced by `ruff`). Note: This deviates from PEP 8's default of 79 characters.
 - **Typing**: Use type hints for all function signatures and complex variables.
ForLLMsAndAgents/guidelines.md-21-26 (1)

21-26: ⚠️ Potential issue | 🟡 Minor

Fix formatting inconsistency in commit convention example.

Lines 23 and 25 have leading spaces that create formatting inconsistencies in the commit structure example. Remove these for cleaner presentation.

🎨 Proposed formatting fix
 - **Format**: [Subject Line]
 
-  [Body]
+[Body]
 
-  [Footer/Issues]
+[Footer/Issues]
BinaryOptionsToolsV2/python/BinaryOptionsToolsV2/validator.py-29-29 (1)

29-29: ⚠️ Potential issue | 🟡 Minor

Docstring regex examples have double-escaped patterns in raw strings.

r"[A-Z]\\w+" in a raw string produces the literal string [A-Z]\\w+ (two backslashes), not [A-Z]\w+. The regex engine will match a literal backslash followed by w, not the \w character class. Same issue on line 54 with r"^\\d+".

Use either r"\w" (raw string, single backslash) or "\\w" (regular string, escaped), but not both.

📝 Proposed fix
-        v1 = Validator.regex(r"[A-Z]\\w+")  # Starts with capital letter
+        v1 = Validator.regex(r"[A-Z]\w+")  # Starts with capital letter
-            validator = Validator.regex(r"^\\d+")
+            validator = Validator.regex(r"^\d+")
CHANGELOG.md-158-161 (1)

158-161: ⚠️ Potential issue | 🟡 Minor

Missing reference link for [0.2.6].

The heading on line 22 uses [0.2.6] but no corresponding link definition exists in the footer. The other versions all have their links defined here.

Proposed fix
+[0.2.6]: https://github.com/ChipaDevTeam/BinaryOptionsTools-v2/releases/tag/BinaryOptionsToolsV2-0.2.6
 [0.2.5]: https://github.com/ChipaDevTeam/BinaryOptionsTools-v2/releases/tag/BinaryOptionsToolsV2-0.2.5
.github/workflows/CI.yml-155-169 (1)

155-169: ⚠️ Potential issue | 🟡 Minor

Consider pinning the Alpine version for reproducibility and stability.

The alpine:latest image on line 159 is unpinned, which can introduce non-deterministic behavior if Alpine's base image changes. While python3 -m venv will work correctly with py3-pip installed (since py3-pip depends on python3, which includes the bundled ensurepip module), pinning to a specific Alpine version like alpine:3.20 is a best practice for CI stability.

-          docker run --rm -v ${{ github.workspace }}:/io -w /io/BinaryOptionsToolsV2 alpine:latest sh -c '
+          docker run --rm -v ${{ github.workspace }}:/io -w /io/BinaryOptionsToolsV2 alpine:3.20 sh -c '
crates/binary_options_tools/src/pocketoption/modules/keep_alive.rs-1-16 (1)

1-16: ⚠️ Potential issue | 🟡 Minor

SID constant is unused in run(), creating a routing/processing mismatch.

SID (40{"sid":) is used in InitRule::call (line 214) for routing, but run() uses the broader text.starts_with("40") (line 90). This means rule() will only route 40{"sid":...} messages to InitModule, yet run() is written to handle any 40-prefixed message. If the server ever sends a plain 40 (a valid Socket.IO connect ack without SID), rule() won't route it, and the authentication flow stalls.

Consider aligning the routing rule with what run() expects:

Proposed fix in InitRule::call
                if text.starts_with(SID_BASE)
-                    || text.starts_with(SID)
+                    || text.starts_with("40")
                    || text.as_str() == "41"
                    || text.as_str() == "2"
crates/binary_options_tools/src/pocketoption/modules/keep_alive.rs-248-253 (1)

248-253: ⚠️ Potential issue | 🟡 Minor

Stale valid flag can cause the binary part of successauth to be missed.

The fallback at lines 249–252 consumes the valid flag for any text message that doesn't match the protocol patterns above. If any unexpected text message (e.g., a server notification without [) arrives between the successauth placeholder text and its binary counterpart, this block will consume valid and return true for the wrong message. The subsequent binary part will then not be routed to InitModule, and authentication won't complete.

This is an edge-case ordering issue, but it could cause silent auth failures that are very hard to diagnose.

Suggested fix: only consume `valid` for binary messages
             }
         }
-
-                if self.valid.load(Ordering::SeqCst) {
-                    self.valid.store(false, Ordering::SeqCst);
-                    return true;
-                }
                 false
             }
             Message::Binary(_) => {

This way, only Message::Binary (lines 255–261) can consume the flag, which is the intended next message type after a binary-placeholder successauth.

crates/binary_options_tools/src/pocketoption/modules/keep_alive.rs-47-189 (1)

47-189: ⚠️ Potential issue | 🟡 Minor

Add clarifying comment about InitModule's persistent loop design.

The run() loop correctly persists beyond successful authentication at line 156 to continue handling protocol-level keep-alive messages (e.g., Socket.IO pings at lines 132–134). However, this intent is not documented in the code. Since the module name and structure suggest dual-purpose behavior (initialization and keep-alive), add a comment above or within the authentication block (around line 155) clarifying that the loop intentionally persists to maintain the WebSocket connection and handle ongoing protocol messages:

// Module continues running to handle protocol keep-alive (Socket.IO pings/pongs)
// and to route subsequent data messages to other modules via their rules
authenticated = true;

This prevents future maintainers from incorrectly refactoring away the persistent loop.

crates/core/data/client2.rs-1-3 (1)

1-3: ⚠️ Potential issue | 🟡 Minor

Unused import: f32::consts::E.

E from f32::consts is not referenced anywhere in this file. Remove it to avoid a compiler warning.

Proposed fix
 use std::{
-    collections::HashMap, f32::consts::E, ops::Deref, sync::Arc, time::{Duration, Instant}
+    collections::HashMap, ops::Deref, sync::Arc, time::{Duration, Instant}
 };
BinaryOptionsToolsV2/python/BinaryOptionsToolsV2/pocketoption/synchronous.py-454-458 (1)

454-458: ⚠️ Potential issue | 🟡 Minor

Docstring uses await in a synchronous class.

Line 456 shows await client.connect() in the example, but this is PocketOption (sync). Should be client.connect().

Fix
-            await client.connect()
+            client.connect()
BinaryOptionsToolsV2/python/BinaryOptionsToolsV2/pocketoption/asynchronous.py-193-205 (1)

193-205: ⚠️ Potential issue | 🟡 Minor

SSID sanitization looks reasonable; minor note on credential logging.

The regex normalization on line 195 is a good defensive measure for common shell-stripping issues. However, line 203 logs the first 20 characters of the SSID. Depending on the SSID format, this could include sensitive session token data. Consider logging only that the SSID "does not start with '42['" without including the actual prefix content.

Suggested change
-            self.logger.warn(f"SSID does not start with '42[': {ssid[:20]}...")
+            self.logger.warn("SSID does not start with expected '42[' prefix")
BinaryOptionsToolsV2/python/BinaryOptionsToolsV2/tracing.py-132-159 (1)

132-159: ⚠️ Potential issue | 🟡 Minor

Docstring claims the function raises but the implementation swallows all exceptions.

Lines 144–145 document Raises: Exception, but the actual behavior (lines 158–159) catches every exception and only prints it. The caller has no signal that logging initialization failed. Also, the layers parameter is missing from the docstring's Args section.

If silencing errors is intentional (to avoid crashing on logging failures), remove the Raises section from the docstring and consider returning a bool or logging a warning via warnings.warn. If errors should propagate, remove the bare except.

Suggested docstring fix (if swallowing is intentional)
     """
     Initialize logging system for the application.
 
     Args:
         path (str): Path where log files will be stored.
         level (str): Logging level (default is "DEBUG").
         terminal (bool): Whether to display logs in the terminal (default is True).
+        layers (list): Additional log layers to configure (default is empty list).
 
     Returns:
         None
-
-    Raises:
-        Exception: If there's an error starting the logging system.
+
+    Note:
+        Errors during initialization are printed to stdout but not raised.
     """
crates/core/data/client_enhanced.rs-839-853 (1)

839-853: ⚠️ Potential issue | 🟡 Minor

messages_sent counter incremented before the message is actually enqueued.

The counter is bumped (line 843) before message_sender.send() (line 847). If the send fails, the counter still reflects the failed message, making stats inaccurate.

Proposed fix: increment after successful send
     pub async fn send_message(&self, message: Message) -> BinaryOptionsResult<()> {
-        // Update stats
-        {
-            let mut state = self.connection_state.write().await;
-            state.messages_sent += 1;
-        }
-
         // Send through message batcher or directly
         self.message_sender
             .send(message)
             .await
             .map_err(|e| BinaryOptionsToolsError::ChannelRequestSendingError(e.to_string()))?;
 
+        // Update stats after successful enqueue
+        {
+            let mut state = self.connection_state.write().await;
+            state.messages_sent += 1;
+        }
+
         Ok(())
     }
crates/core/data/client_enhanced.rs-485-488 (1)

485-488: ⚠️ Potential issue | 🟡 Minor

Misleading error variant when all connection attempts fail.

Returning tungstenite::Error::ConnectionClosed when no connection was ever established is semantically incorrect — this error typically means an existing connection was closed. Consider using a more descriptive error (e.g., a custom variant or Error::Url / Error::Io) or returning the last encountered connection error instead.

Suggested approach: propagate the last error
+        let mut last_error = None;
         for url in &self.connection_urls {
             match self.try_connect_single(url).await {
                 Ok(websocket) => {
                     // ... existing success handling ...
                 }
                 Err(e) => {
                     warn!("Failed to connect to {}: {}", url, e);
+                    last_error = Some(e);
                     continue;
                 }
             }
         }
 
-        Err(BinaryOptionsToolsError::WebsocketConnectionError(
-            tokio_tungstenite::tungstenite::Error::ConnectionClosed,
-        ))
+        Err(last_error.unwrap_or_else(|| BinaryOptionsToolsError::WebsocketConnectionError(
+            tokio_tungstenite::tungstenite::Error::ConnectionClosed,
+        )))
crates/binary_options_tools/src/expertoptions/types.rs-53-55 (1)

53-55: ⚠️ Potential issue | 🟡 Minor

get_symbol() fallback to name could cause silent HashMap key collisions in Assets::new.

If multiple assets have symbol: None and share the same name, or if one asset's symbol matches another's name, the HashMap::from_iter on line 60-65 will silently discard all but the last entry. Consider whether this is an expected scenario from the ExpertOptions API, and if not, adding dedup detection or logging a warning.

crates/binary_options_tools/src/expertoptions/connect.rs-41-41 (1)

41-41: ⚠️ Potential issue | 🟡 Minor

Wrong log target: "PocketConnect" should be "ExpertConnect" (or "ExpertConnectThread").

This logs a successful ExpertOptions connection under the "PocketConnect" target, which is misleading for anyone filtering logs by target. Line 44 has the same pre-existing issue.

Proposed fix
-                    debug!(target: "PocketConnect", "Successfully connected to ExpertOptions");
+                    debug!(target: "ExpertConnect", "Successfully connected to ExpertOptions");
crates/binary_options_tools/src/framework/virtual_market.rs-220-241 (1)

220-241: ⚠️ Potential issue | 🟡 Minor

Potential TOCTOU: trade removal races with a concurrent result() call.

At lines 236–238, the trade is removed from open_trades if current_time >= expiry_time, but the current_time snapshot is taken inside the same lock scope. If two concurrent result() calls execute for the same trade_id, the second call will fail with "Trade not found" because the first already removed it. This is probably acceptable for a virtual market, but worth noting.

crates/binary_options_tools/src/pocketoption/candle.rs-115-131 (1)

115-131: ⚠️ Potential issue | 🟡 Minor

Silent zero defaults on malformed tick data.

as_f64().unwrap_or_default() silently maps unparseable values to 0 (timestamp) or 0.0 (price), which could produce nonsensical candles. Consider logging a warning or filtering out zero-timestamp ticks downstream if data integrity matters.

crates/binary_options_tools/src/pocketoption/utils.rs-59-67 (1)

59-67: ⚠️ Potential issue | 🟡 Minor

Low collision resistance in get_index.

The index is "{unix_seconds}{2-digit random}", yielding only ~90 distinct values per second. If multiple trades or messages are initiated concurrently within the same second, collisions are likely. If uniqueness is required, consider using a wider random range or an atomic counter.

crates/binary_options_tools/src/pocketoption/modules/trades.rs-66-95 (1)

66-95: ⚠️ Potential issue | 🟡 Minor

trade() can block indefinitely if the server never responds.

The rx.await on line 91 has no timeout. If the server never sends a successopenOrder or failopenOrder response, the caller will wait forever. Unlike DealsHandle::check_result_with_timeout, there's no timeout variant here. Consider adding an internal timeout or documenting that callers must wrap this in tokio::time::timeout.

crates/binary_options_tools/src/pocketoption/modules/subscriptions.rs-491-500 (1)

491-500: ⚠️ Potential issue | 🟡 Minor

Severe formatting issue in the History command handler.

Lines 491–500 have wildly inconsistent indentation (deeply indented else if and else branches). This appears to be an accidental formatting artifact. Run rustfmt to fix.

crates/binary_options_tools/src/pocketoption/modules/historical_data.rs-354-357 (1)

354-357: ⚠️ Potential issue | 🟡 Minor

Binary placeholder detection doesn't track expected state.

When is_binary_placeholder is true, the code continues without recording that the next message should be the binary payload. If an unrelated message arrives before the binary payload, it will be processed (or discarded) normally, and the binary payload will be treated as a new message without the context of the pending request. This works because the pending_request is preserved (not taken), so the binary payload will still match. However, if the binary payload is preceded by another ServerResponse::History for a different asset, it could be incorrectly matched first and consume the pending request.

crates/binary_options_tools/src/pocketoption/types.rs-152-159 (1)

152-159: ⚠️ Potential issue | 🟡 Minor

Zero-price fallback from Decimal::from_f64_retain could silently corrupt data.

If as_f64() returns None (e.g., the JSON value is a string or object), price_f64 becomes 0.0 and price becomes Decimal(0). A zero price on a stream data point is semantically invalid for a trading instrument and could trigger incorrect trading logic downstream. Consider returning a deserialization error instead of defaulting.

Suggested approach
-        let price_f64 = vec[0][2].as_f64().unwrap_or(0.0);
-        let price = Decimal::from_f64_retain(price_f64).unwrap_or_default();
+        let price_f64 = vec[0][2]
+            .as_f64()
+            .ok_or_else(|| serde::de::Error::custom("Invalid price value in StreamData"))?;
+        let price = Decimal::from_f64_retain(price_f64)
+            .ok_or_else(|| serde::de::Error::custom("Failed to convert price to Decimal"))?;
crates/binary_options_tools/src/lib.rs-24-27 (1)

24-27: ⚠️ Potential issue | 🟡 Minor

Unclosed code fence in crate-level doc comment.

Line 27 opens a ```text block, but the doc comments end immediately after — there's no closing ```. This will produce a rustdoc warning and render the remaining crate documentation incorrectly. The example comment lines (24–26) likely belong inside the fenced block.

Proposed fix
-//! // Use the streaming utilities for real-time data processing
-//! // Serialize and deserialize data with the provided macros
-//! // Apply timeouts to async operations
-//! ```text
+//! ```text
+//! // Use the streaming utilities for real-time data processing
+//! // Serialize and deserialize data with the provided macros
+//! // Apply timeouts to async operations
+//! ```

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In
`@BinaryOptionsToolsV2/python/BinaryOptionsToolsV2/pocketoption/synchronous.py`:
- Around line 224-237: The context manager __exit__ currently calls shutdown()
and thus leaks the event loop; change __exit__ to call self.close() so both
shutdown and loop cleanup run. In close(), remove the dead conditional that
calls self.loop.stop() after shutdown (since shutdown uses run_until_complete
and the loop will not be running), and simply perform shutdown (if not already
done) then safely close the loop (self.loop.close()), guarding if needed for
None or already-closed state; reference methods: __exit__, close, shutdown and
attribute self.loop.

- Enhanced connection management with improved statistics tracking and connection pooling.
- Updated `BinaryOptionsToolsError::WebsocketConnectionError` to box the underlying error type.
- Introduced breaking changes in the WebSocket event system, including unification of event handling.
- Added explicit request ID registration in the ResponseRouter for improved response handling.
- Updated Python type hints to reflect correct return types for trading and data methods.
- Increased pytest timeout to 60 seconds for better test stability.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (9)
crates/core/data/connection.rs (2)

281-288: ⚠️ Potential issue | 🟠 Major

Clone creates a fresh empty pool, silently discarding all accumulated stats.

The Clone impl constructs a brand-new ConnectionPool (with empty connections and stats maps) instead of sharing the Arc<Mutex<…>> state. This means every call to self.clone() on line 242 gives the spawned task a manager whose pool.update_stats(…) writes go nowhere useful — the stats are thrown away when the task finishes. Consequently, get_best_url() on the original manager will never see data collected during parallel connection attempts.

If the intent is for clones to share the pool, derive or implement Clone by cloning the Arcs:

Proposed fix
 impl Clone for EnhancedConnectionManager {
     fn clone(&self) -> Self {
         Self {
-            pool: ConnectionPool::new(self.pool.max_connections),
+            pool: self.pool.clone(),
             connect_timeout: self.connect_timeout,
             ssl_verify: self.ssl_verify,
         }
     }
 }

ConnectionPool already has Arc-wrapped fields, so deriving Clone (or writing the above) will share the underlying maps. You may also need to derive or implement Clone on ConnectionPool:

impl Clone for ConnectionPool {
    fn clone(&self) -> Self {
        Self {
            connections: Arc::clone(&self.connections),
            stats: Arc::clone(&self.stats),
            max_connections: self.max_connections,
        }
    }
}

181-185: ⚠️ Potential issue | 🟡 Minor

SSL verification bypass is a no-op — both branches produce Connector::default().

The ssl_verify flag has no effect. If disabling verification isn't needed yet, consider removing the flag to avoid giving callers a false sense of configurability, or track this TODO in an issue.

Would you like me to open an issue to track implementing the SSL verification bypass?

crates/binary_options_tools/src/pocketoption/modules/subscriptions.rs (3)

747-761: ⚠️ Potential issue | 🟡 Minor

Orphaned router entry when sender is None.

self.router.register(command_id) at line 750 inserts a oneshot::Sender into the router's pending map. If self.sender is None (line 751), the method returns early at line 760 without ever consuming or removing that entry, leaking it.

Move the registration after confirming the sender exists, or guard it:

Proposed fix
     pub async fn unsubscribe(mut self) -> PocketResult<()> {
         let command_id = Uuid::new_v4();
-        let receiver = self.router.register(command_id).await;
         if let Some(sender) = self.sender.take() {
+            let receiver = self.router.register(command_id).await;
             sender
                 .send(Command::Unsubscribe {
                     asset: self.asset.clone(),

502-521: 🛠️ Refactor suggestion | 🟠 Major

Severely broken indentation in the History command handler.

Lines 512–521 are indented far deeper than the surrounding code (~48 spaces vs ~24). This appears to be an accidental formatting artifact and hurts readability. Please reformat this block to match the surrounding indentation level.


852-913: ⚠️ Potential issue | 🟠 Major

Drop unsubscribes on every clone, which may terminate sibling streams.

SubscriptionStream implements Clone (line 853) and Drop sends an Unsubscribe command (line 903). If a stream is cloned and one clone is dropped (e.g., moved into a closure, temporary variable, etc.), the Drop fires an unsubscribe for the asset, tearing down the subscription for all remaining clones that share the same asset.

Consider tracking ownership more carefully—e.g., use an Arc-based refcount and only unsubscribe when the last reference is dropped, or remove the automatic unsubscribe from Drop entirely and require explicit calls to unsubscribe().

BinaryOptionsToolsV2/python/BinaryOptionsToolsV2/BinaryOptionsToolsV2.pyi (1)

59-115: ⚠️ Potential issue | 🟡 Minor

RawPocketOption stub is missing shutdown, get_pending_deals, and active_assets methods.

The async wrapper (asynchronous.py lines 461, 549, 745) calls await self.client.shutdown(), await self.client.get_pending_deals(), and await self.client.active_assets() on the raw client. These methods are exported from the Rust implementation (pocket_client.rs) but are absent from this type stub, causing type-checker errors.

Add missing method signatures
     async def unsubscribe(self, asset: str) -> None: ...
     async def create_raw_handler(self, validator: RawValidator, keep_alive: Optional[str]) -> RawHandler: ...
+    async def shutdown(self) -> None: ...
+    async def get_pending_deals(self) -> str: ...
+    async def active_assets(self) -> str: ...
crates/core/data/client_enhanced.rs (3)

527-550: ⚠️ Potential issue | 🔴 Critical

Remove the unused try_connect_single method (lines 527–550).

This method is dead code. The connect() method uses self.connector.connect() directly instead of calling this URL-based wrapper, and no other code in the repository calls try_connect_single from this file. Remove it to reduce clutter.


518-521: ⚠️ Potential issue | 🔴 Critical

Type mismatch: KeepAliveManager::start expects Sender<Message> but receives SenderMessage.

The call at line 520 passes self.message_sender.clone() (which returns SenderMessage) to KeepAliveManager::start, which expects Sender<Message>. SenderMessage does not implement Deref, Into, or any conversion trait that would make this compile. Either extract the inner sender field or modify KeepAliveManager::start to accept SenderMessage.


465-506: ⚠️ Potential issue | 🔴 Critical

URL-based region fallback is a no-op: connector.connect() never receives the target URL.

The loop iterates connection_urls but connector.connect(...) (line 469) only receives credentials and config—the url is never passed. Every iteration attempts an identical connection, defeating the region-fallback design.

The codebase already has the proper mechanism: self.connection_manager.connect(&self.connection_urls) handles URL-based fallback correctly (see try_connect_single() at line 525 for reference). Replace the ineffective loop with a direct call to the connection manager:

Suggested fix
-        // Try each URL in sequence (like Python)
-        for url in &self.connection_urls {
-            // First try authenticated connect using the connector
-            match self
-                .connector
-                .connect::<T, Transfer, U>(self.credentials.clone(), &self.config)
-                .await
-            {
-                Ok(websocket) => {
+        // Use connection manager to handle URL-based fallback
+        let (websocket, used_url) = self
+            .connection_manager
+            .connect(&self.connection_urls)
+            .await?;
+        
+        {
                    info!(
                         "Connected and authenticated to region: {}",
-                        url.host_str().unwrap_or("unknown")
+                        Url::parse(&used_url)?.host_str().unwrap_or("unknown")
                     );

                     // Update connection state
                     let mut state = self.connection_state.write().await;
                     state.is_connected = true;
                     state.successful_connections += 1;
                     state.connection_start_time = Some(Instant::now());
-                    state.current_region = url.host_str().map(|s| s.to_string());
+                    state.current_region = Url::parse(&used_url)?.host_str().map(|s| s.to_string());
                     state.reconnect_attempts = 0;
                     drop(state);

                     // Emit connected event
                     self.event_manager
                         .emit(Event::new(
                             EventType::Connected,
-                            serde_json::json!({"region": url.host_str()}),
+                            serde_json::json!({"region": Url::parse(&used_url)?.host_str()}),
                         ))
                         .await?;

                     // Start connection handler
                     self.start_connection_handler(websocket).await?;
                     return Ok(());
-                }
-                Err(e) => {
-                    warn!(
-                        "Failed to connect/authenticate to {}: {}, trying next URL",
-                        url, e
-                    );
-                    continue;
-                }
-            }
         }
-
-        Err(BinaryOptionsToolsError::WebsocketConnectionError(Box::new(
-            tokio_tungstenite::tungstenite::Error::ConnectionClosed,
-        )))
🤖 Fix all issues with AI agents
In `@crates/binary_options_tools/src/pocketoption/modules/subscriptions.rs`:
- Around line 805-810: In process_update, avoid silently converting Decimal→f64
with price.to_f64().unwrap_or_default() — if conversion fails return a
PocketResult::Err instead of using 0.0; change the conversion to check
price.to_f64().ok_or_else(...) and propagate a descriptive error (including the
asset from self.asset() and timestamp) so the subsequent call to
self.sub_type.update(&BaseCandle::from((timestamp, price_f64)))? never receives
an invalid zero price.

In `@crates/core/data/client_enhanced.rs`:
- Around line 591-597: The current code uses futures_util::stream::select_all
(fused_streams) to merge RecieverStream::new(message_receiver) and
RecieverStream::new(message_receiver_priority), which yields fair (round-robin)
polling and loses priority semantics; change the loop inside the tokio::spawn
task to poll the two streams explicitly with a biased tokio::select! so
message_receiver_priority is drained first. Concretely: create mutable stream
handles (e.g., let mut normal = stream1.to_stream(); let mut priority =
stream2.to_stream()), then replace the while let Some(Ok(message)) =
fused_streams.next().await loop with a loop using tokio::select! (biased) that
first tries to receive from priority and handles Some(Ok(msg)) there, and only
if priority is empty awaits normal.next() as the fallback; keep the same message
handling code paths for the matched branches.

In `@crates/core/data/client2.rs`:
- Around line 354-363: The remove_handler stub currently does nothing; implement
real removal by identifying handlers by name: add a name() -> &str (or owned
String) to the EventHandler trait (or introduce a thin NamedEventHandler
wrapper/type) so each Arc<dyn EventHandler<WebSocketEvent<T::Transfer>>> exposes
an identifier, then change add_handler/remove_handler to use that identifier; in
remove_handler acquire self.event_handlers.write().await, iterate/retain to drop
handlers whose name() matches the provided &str and return true if any were
removed (false otherwise); update callers such as
WebSocketClient2::remove_event_handler to expect the real bool result.
- Around line 276-292: The loop in client2.rs currently breaks once tasks.len()
>= config.max_concurrent_handlers, silently dropping remaining handlers and
always wrapping events as EventType::Custom("ws_event"), so change the dispatch
in the function that iterates over handlers to (1) remove the break and instead
enforce concurrency with a limiter (e.g., use a tokio::sync::Semaphore, or
collect spawned futures into a FuturesUnordered and drive at most
config.max_concurrent_handlers concurrently) so every handler in handlers is
eventually awaited and executed, and (2) preserve the original event variant
when creating the Event (stop unconditionally using
EventType::Custom("ws_event".to_string()) and pass the correct EventType through
to Event::new); keep using handler.clone(), handler.handle(&e).await and log
errors as before.

In `@crates/core/data/connection.rs`:
- Around line 269-271: The code currently constructs and returns
BinaryOptionsToolsError::WebsocketConnectionError wrapping
tokio_tungstenite::tungstenite::Error::ConnectionClosed when all URL attempts
fail, which is misleading; update the connection loop (the function that
iterates URLs and returns the final Err) to capture and return the last actual
error from the failed attempts instead of fabricating ConnectionClosed, or add
and return a clearer error variant like
BinaryOptionsToolsError::AllConnectionsFailed with a message and optionally the
last error; ensure you replace the hardcoded
tokio_tungstenite::tungstenite::Error::ConnectionClosed instantiation and use
the real last_error variable (or construct the new variant) when calling
WebsocketConnectionError (or the new variant).
🧹 Nitpick comments (11)
crates/core/data/connection.rs (1)

252-267: All individual connection errors are silently discarded in the parallel path.

Both Ok(Err(_)) and Err(_) arms are silently continued. When every attempt fails, the caller gets only the synthetic ConnectionClosed error (flagged above) with no context about why connections failed. Consider collecting or logging at least the last error for debuggability.

crates/binary_options_tools/src/framework/virtual_market.rs (2)

62-214: buy and sell are nearly identical — consider extracting a shared helper.

The two methods differ only in Action::Call/Action::Put and command: 0/1. A private method like open_trade(&self, asset, amount, time, action) would eliminate ~75 duplicated lines and make future changes (e.g., adding fields to Deal) less error-prone. The same applies to the four repeated Deal construction blocks across buy, sell, and result.


291-302: Note: close_price reflects the price at query time, not at trade expiry.

This is pre-existing behavior unchanged by this PR, but worth documenting: if result() is called significantly after expiry, the close price will be whatever the market price happens to be at that moment rather than the price at the actual expiry timestamp. For a virtual/testing market this may be acceptable, but callers should be aware.

crates/binary_options_tools/src/pocketoption/modules/subscriptions.rs (1)

42-57: Spawned router task has no cancellation handle.

The JoinHandle from tokio::spawn is dropped, so there's no way to await or cancel the background task. This is acceptable while the AsyncReceiver channel provides an implicit shutdown signal, but if graceful-shutdown semantics become important later, consider storing the handle (or using a CancellationToken).

BinaryOptionsToolsV2/python/BinaryOptionsToolsV2/BinaryOptionsToolsV2.pyi (1)

73-75: Return type change from Tuple to List[str] for buy/sell is functionally correct but semantically looser.

List[str] works for unpacking at runtime, but since the Rust side always returns exactly 2 elements (trade_id, trade_json), a Tuple[str, str] would give type checkers better guarantees and match the unpacking pattern in asynchronous.py ((trade_id, trade) = await self.client.buy(...)).

crates/core/data/client2.rs (2)

700-726: write is now moved into the sender task — previous Clone issue resolved.

The SplitSink ownership is correctly transferred into the spawned task.

One minor note: message.clone() on line 708 is unnecessary since message is owned from recv() and could be passed directly to write.send(message).

Remove unnecessary clone
-                    if let Err(e) = write.send(message.clone()).await {
+                    if let Err(e) = write.send(message).await {

313-339: Remove the unused WebSocketConfig struct from client2.rs.

The WebSocketConfig defined at lines 313–328 is dead code and never instantiated. The canonical WebSocketConfig (with connection settings, health monitoring, and performance options) lives in websocket_config.rs and is actively used throughout the codebase. Keeping the duplicate struct in client2.rs creates confusion between two similarly-named types with completely different fields and purposes.

crates/core/data/client_enhanced.rs (4)

647-728: Dual close-frame detection paths are confusing and may double-emit events.

The handler pipeline (lines 652-663) can detect close frames and emit Disconnected + trigger reconnect. Then, lines 704-714 perform a second raw Message::Close match doing the same thing. If the handler sets should_close = true, the break at line 662 prevents reaching line 705, so no double-emit. But if the handler doesn't flag close, both paths exist for the same concern.

Consider consolidating: either let the handler fully own close detection (remove the raw match for Message::Close), or remove close handling from the handler and rely solely on the raw match.


597-598: Unnecessary message.clone() — the value can be moved.

message isn't used after write.send(...), so the clone allocates needlessly on every message. Just move it:

Suggested fix
-                match write.send(message.clone()).await {
+                match write.send(message).await {

903-939: disconnect doesn't send a WebSocket Close frame before aborting tasks.

The method is documented as "disconnect gracefully," but it aborts background tasks (including the sender) without first sending a Message::Close(None) through the WebSocket. Consider queuing a close frame and giving the sender a brief window to flush it before aborting.


757-884: Remove process_text_message, process_socket_io_message, and handle_json_message — they're unused.

These methods form an internal chain but are never called. The receiver task at line 623 routes incoming messages through handler.process_message() instead. Remove these methods to reduce maintenance burden.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (9)
crates/binary_options_tools/src/pocketoption/modules/subscriptions.rs (4)

747-761: ⚠️ Potential issue | 🟡 Minor

Oneshot sender leaked when self.sender is None.

register inserts a sender into the router's pending map on line 750, but if self.sender is already None (line 751), the function returns early without ever consuming the registered oneshot. The orphaned entry sits in the pending HashMap until the router task shuts down.

Move the registration after the sender check, or remove the pending entry on the early-return path.

Proposed fix
     pub async fn unsubscribe(mut self) -> PocketResult<()> {
         let command_id = Uuid::new_v4();
-        let receiver = self.router.register(command_id).await;
         if let Some(sender) = self.sender.take() {
+            let receiver = self.router.register(command_id).await;
             sender
                 .send(Command::Unsubscribe {
                     asset: self.asset.clone(),

858-868: ⚠️ Potential issue | 🟠 Major

Clone + Drop interaction will prematurely kill subscriptions.

SubscriptionStream implements both Clone (clones the sender/router/asset) and Drop (sends Command::Unsubscribe for self.asset). Dropping any clone unsubscribes the asset for all clones — the surviving clone's next receive() will get a Terminated event.

Consider either:

  • Removing the Clone impl entirely (use Arc<SubscriptionStream> if sharing is needed), or
  • Reference-counting the sender (e.g., with Arc<AtomicUsize>) and only sending Unsubscribe when the last clone is dropped.

502-521: ⚠️ Potential issue | 🟡 Minor

Severely broken indentation in the History command handler.

Lines 512–521 are indented ~48 spaces deep while the surrounding code is at ~24. This looks like a paste/merge artifact and makes the block very hard to follow. Please reformat.


829-834: ⚠️ Potential issue | 🟠 Major

to_stream never terminates — yields infinite errors after a failure.

unfold always returns Some(...), so after a terminal error (channel closed, Terminated event) the stream will repeatedly call receive() and yield Err on every poll, spinning forever.

Return None on unrecoverable errors to signal stream completion:

Proposed fix
     pub fn to_stream(self) -> impl futures_util::Stream<Item = PocketResult<Candle>> + 'static {
         Box::pin(unfold(self, |mut stream| async move {
             let result = stream.receive().await;
-            Some((result, stream))
+            match result {
+                Ok(candle) => Some((Ok(candle), stream)),
+                Err(e) => {
+                    // Yield the error and then terminate the stream
+                    Some((Err(e), stream.into_terminated()))
+                }
+            }
         }))
     }

Alternatively, a simpler approach — just stop on error:

     pub fn to_stream(self) -> impl futures_util::Stream<Item = PocketResult<Candle>> + 'static {
         Box::pin(unfold(Some(self), |state| async move {
             let mut stream = state?;
             match stream.receive().await {
                 Ok(candle) => Some((Ok(candle), Some(stream))),
                 Err(e) => Some((Err(e), None)),  // yield error, then end
             }
         }))
     }
crates/core/data/client2.rs (2)

3-3: ⚠️ Potential issue | 🟡 Minor

Unused import: std::f32::consts::E.

This import of Euler's number is not used anywhere in the file.

 use std::{
     collections::HashMap,
-    f32::consts::E,
     ops::Deref,
     sync::Arc,
     time::{Duration, Instant},
 };

630-636: ⚠️ Potential issue | 🔴 Critical

connected_at field does not exist on ConnectionState — will not compile.

ConnectionState (lines 103–133) has connection_start_time: Option<Instant>, not connected_at. The same issue appears at line 669.

🐛 Proposed fix

At line 635:

                         shared_state
                             .update_stats(|stats| {
                                 stats.successful_connections += 1;
-                                stats.connected_at = Some(std::time::Instant::now());
+                                stats.connection_start_time = Some(std::time::Instant::now());
                             })
                             .await;

At line 669:

-                                stats.connected_at = None;
+                                stats.connection_start_time = None;
crates/core/data/client_enhanced.rs (2)

459-507: ⚠️ Potential issue | 🔴 Critical

connect() ignores the iterated URL — all loop iterations attempt the same connection.

The loop iterates over self.connection_urls (line 465) but self.connector.connect() (lines 467–471) uses self.credentials and self.config, completely ignoring the url variable. Every iteration makes the exact same connection attempt, so the URL-based region fallback is non-functional. The url is only used for logging (line 474) and setting current_region (line 483).

The connection should either pass the URL to the connector or use try_connect_single(url) (defined at line 527).


360-374: ⚠️ Potential issue | 🔴 Critical

Closure signature mismatch with EventHandler trait — this code will not compile.

The add_event_handler accepts F: Fn(&serde_json::Value) -> BinaryOptionsResult<()>, but EventManager::add_handler (events.rs:121) expects Arc<dyn EventHandler<T>>. The EventHandler blanket impl (events.rs:85-94) requires F: Fn(&Event<T>) -> Fut where Fut: Future<Output = BinaryOptionsResult<()>>.

Your closure doesn't satisfy this:

  • Parameter mismatch: takes &serde_json::Value, needs &Event<serde_json::Value>
  • Return type mismatch: returns BinaryOptionsResult<()> directly, needs Future<Output = BinaryOptionsResult<()>>

The Arc<F> cannot be coerced to Arc<dyn EventHandler<serde_json::Value>>.

crates/core/data/connection.rs (1)

251-276: ⚠️ Potential issue | 🔴 Critical

Type mismatch: last_error is Option<BinaryOptionsToolsError> but unwrap_or receives TungsteniteError.

At line 266, Ok(Err(e)) yields e: BinaryOptionsToolsError, making last_error: Option<BinaryOptionsToolsError>. However, line 275 calls last_error.unwrap_or(tokio_tungstenite::tungstenite::Error::ConnectionClosed), which passes TungsteniteError to unwrap_or. Since unwrap_or requires its argument to match the Option's inner type, this causes a compilation error.

Fix by using unwrap_or_else to construct the fallback error:

Proposed fix
         Err(BinaryOptionsToolsError::WebsocketConnectionError(Box::new(
-            last_error.unwrap_or(tokio_tungstenite::tungstenite::Error::ConnectionClosed),
+            last_error.unwrap_or_else(|| {
+                BinaryOptionsToolsError::WebsocketConnectionError(Box::new(
+                    tokio_tungstenite::tungstenite::Error::ConnectionClosed,
+                ))
+            }),
         )))
🤖 Fix all issues with AI agents
In `@CHANGELOG.md`:
- Around line 22-39: The changelog entry currently labels a release as "0.2.6"
but contains multiple breaking changes and new features (see sections "Added"
and "Changed (Breaking Logic)" including symbols Deal.profit, EventHandler,
WebSocketEvent, EnhancedWebSocketInner, PocketOption), so update the release
header to a minor version bump (e.g., change "## [0.2.6] - 2026-02-13" to "##
[0.3.0] - 2026-02-13") and ensure any internal references to "0.2.6" in this
file are updated to "0.3.0" to reflect the breaking/API changes per the
repository's semantic versioning policy.
- Around line 176-179: Add the missing release link for 0.2.6 to the versions
list in CHANGELOG.md by adding a new line in the same format as the others:
include the reference label "[0.2.6]:" followed by the GitHub release URL using
the existing tag naming pattern (BinaryOptionsToolsV2-0.2.6) so it matches
entries like [0.2.5] and [0.2.4]; ensure placement is consistent with the other
version links.

In `@crates/core/readme.md`:
- Line 27: Correct the grammatical error in the README line describing Sender:
change the phrase "will work be shared between threads" to a grammatically
correct form such as "will be shared between threads" (or "is designed to be
shared between threads") in the sentence that documents `Sender` so the
description reads clearly.
- Line 18: The README's WebSocket trait/struct list is outdated; replace the
incorrect names with the actual implementations: list the Connect trait, the
MessageHandler trait (for message processing), the SenderMessage struct (noting
it wraps an async_channel::Sender for sending), and the Data struct (for data
management) so the README accurately matches the codebase.
🧹 Nitpick comments (6)
crates/binary_options_tools/src/pocketoption/modules/subscriptions.rs (1)

544-552: retain with side-effect to extract command_id works but is fragile.

If multiple history responses arrive for the same (asset, period) — e.g., due to a server-side replay — retain would match and remove only the first entry, but id would be overwritten to the last match's command_id, routing the response to the wrong caller.

This is unlikely given the duplicate-request guard on line 504, but worth noting. A position/swap_remove or drain_filter would be more explicit.

crates/core/data/events.rs (1)

76-94: Closures are not removable by name since the blanket impl always returns "unnamed".

The closure impl<T, F, Fut> EventHandler<T> for F doesn't override name(), so all closure-based handlers share the name "unnamed". Calling remove_handler("unnamed") in client2.rs would remove all closure handlers at once, and removing a specific closure handler is impossible.

Consider either:

  • Documenting this limitation clearly, or
  • Providing a wrapper struct (e.g., NamedHandler { name: String, handler: F }) for registering closures with identifiable names.
crates/core/data/connection.rs (1)

286-293: Clone creates a fresh empty pool, discarding all accumulated stats.

EnhancedConnectionManager::clone() constructs a brand-new ConnectionPool, so cloned instances (e.g., line 242 in connect()) lose all ConnectionStats accumulated by the original. This means the get_best_url() optimization is effectively never used when connections are attempted in parallel, since each spawned task operates on an empty pool.

If losing stats is intentional (to isolate parallel attempts), consider documenting it. Otherwise, share the pool via Arc:

♻️ Suggested approach
 pub struct EnhancedConnectionManager {
-    pool: ConnectionPool,
+    pool: Arc<ConnectionPool>,
     connect_timeout: Duration,
     ssl_verify: bool,
 }

This makes Clone share the same pool and removes the need for a manual Clone impl.

crates/core/data/client2.rs (2)

302-318: All handler tasks are spawned eagerly — semaphore only limits concurrent execution, not spawn count.

Every handler gets a tokio::spawn regardless of max_concurrent_handlers. Under many handlers this could create a large number of tasks. The semaphore correctly throttles concurrent execution, but the spawn overhead remains. This is likely acceptable for typical handler counts (≤ dozens), but worth noting if handler counts grow large.


386-390: remove_handler now works but is fragile with closure handlers.

Since closure-based EventHandler impls always return "unnamed" from name() (see events.rs), calling remove_handler("unnamed") would remove all closure handlers. This is functional but could surprise callers. Consider documenting this limitation or returning an error when name == "unnamed".

crates/core/data/client_enhanced.rs (1)

715-740: Duplicate handling of Message::Close — disconnect event emitted twice and reconnect triggered twice.

After handler.process_message() detects a close frame (line 665 should_close branch), it emits a Disconnected event and calls reconnect_notify.notify_one() then breaks. However, if should_close is false but the raw message is Message::Close, lines 717–727 emit a second Disconnected event and call reconnect_notify.notify_one() again.

Even if should_close is true, the break at line 674 prevents reaching line 716. But if the handler doesn't detect the close (returns should_close = false), the low-level match handles it — this path works. The concern is that two independent close-detection mechanisms could diverge. Consider consolidating close detection in one place.

sixtysixx and others added 2 commits February 13, 2026 03:47
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@sixtysixx
Copy link
Contributor Author

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request is a significant revamp, introducing numerous improvements across the board. Key enhancements include the migration to rust_decimal::Decimal for financial calculations, which greatly improves precision and correctness. The connection logic, SSID parsing, and message handling have been made more robust. New features like pending orders have been added. The codebase has also been reorganized for better maintainability.

My review focuses on the FFI boundaries where Decimal values are converted back to f64. The current use of unwrap_or_default() can lead to silent data corruption, which is a significant risk in a financial application. I've provided several comments with high and medium severity to highlight these areas and suggest safer alternatives.

Comment on lines +236 to +241
amount: deal.amount.to_f64().unwrap_or_default(),
profit: deal.profit.to_f64().unwrap_or_default(),
percent_profit: deal.percent_profit,
percent_loss: deal.percent_loss,
open_price: deal.open_price.to_f64().unwrap_or_default(),
close_price: deal.close_price.to_f64().unwrap_or_default(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Using to_f64().unwrap_or_default() for financial values like amount, profit, and prices is risky. If a Decimal value is outside the range of f64 (e.g., a very large profit), it will be silently converted to 0.0, leading to incorrect data being passed to the FFI consumer. This could have serious consequences in a trading application. It would be safer to use .unwrap() to cause a panic on conversion failure, making the issue immediately visible, rather than allowing potentially incorrect data to be used. A better long-term solution for FFI is often to pass Decimal types as strings to preserve precision.

Comment on lines +325 to +328
open: candle.open.to_f64().unwrap_or_default(),
high: candle.high.to_f64().unwrap_or_default(),
low: candle.low.to_f64().unwrap_or_default(),
close: candle.close.to_f64().unwrap_or_default(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Similar to the Deal struct, using to_f64().unwrap_or_default() for OHLC candle prices can lead to silent data corruption if the decimal values are out of the f64 range. This could cause incorrect chart rendering or flawed technical analysis. It's safer to use .unwrap() to cause a panic on conversion failure, making the issue immediately visible, rather than allowing potentially incorrect data to be used.

Comment on lines +129 to +131
pub async fn balance(&self) -> f64 {
self.inner.balance().await.to_f64().unwrap_or_default()
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The conversion from Decimal to f64 using to_f64().unwrap_or_default() can silently hide potential data corruption. If the balance value is too large or small to be represented by an f64, to_f64() will return None, and this code will then default to 0.0. This could be misleading for the user. Consider returning a Result to propagate the conversion error, or at least logging a warning if the conversion fails.

Comment on lines 136 to 142
pub fn balance<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyAny>> {
let market = self.market.clone();
pyo3_async_runtimes::tokio::future_into_py(py, async move { Ok(market.balance().await) })
pyo3_async_runtimes::tokio::future_into_py(py, async move {
Ok(market.balance().await.to_f64().unwrap_or_default())
})
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The conversion from Decimal to f64 using to_f64().unwrap_or_default() can silently return 0.0 if the balance is too large or small to be represented by an f64. In the context of a trading bot's context object, providing an incorrect balance could lead to flawed strategy decisions. It would be safer to propagate a Python exception if the conversion fails.

@Rick-29 Rick-29 merged commit 92b3d39 into ChipaDevTeam:master Feb 13, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants