Skip to content

fix(stable_api): four correctness bugs — open_pending, sell_option, worker index collision, follow_candle spam#87

Merged
cleitonleonel merged 2 commits intocleitonleonel:masterfrom
AKIB473:fix/stable-api-four-bugs
May 2, 2026
Merged

fix(stable_api): four correctness bugs — open_pending, sell_option, worker index collision, follow_candle spam#87
cleitonleonel merged 2 commits intocleitonleonel:masterfrom
AKIB473:fix/stable-api-four-bugs

Conversation

@AKIB473
Copy link
Copy Markdown
Contributor

@AKIB473 AKIB473 commented May 1, 2026

Problem

Four independent but related correctness bugs in stable_api.py, all discovered during a code review after PR #82. Each bug is documented in a dedicated issue.


Changes

1. open_pending — inverted while/else logic (closes #83)

Python's while/else runs the else block when the loop condition becomes False — i.e. when pending_id was never set (timeout or disconnect). The original code called instruments_follow() there, meaning:

  • On success: instruments_follow was skipped entirely
  • On failure/timeout: a duplicate order was sent — real money risk

Fix: replace while/else with an explicit if self.api.pending_id is not None check after the loop.


2. sell_option — race condition wipes WebSocket response (closes #84)

sold_options_respond was reset to None after the request was sent. On low-latency connections the WS response arrives before the reset line executes, gets silently overwritten, and the poll loop raises TimeoutError every time.

Fix: move the sentinel reset to before send — consistent with how candles_data and other sentinels are handled elsewhere in the codebase.


3. get_historical_candles — worker index collision (closes #85)

Workers computed:

index = int(time.time() * 1000) + worker_id

Two workers (or two iterations within the same worker) running in the same millisecond produce identical indices. The event registry keys responses on this index, so workers silently steal each other's data — wrong historical candles returned with no error raised.

Fix: a module-level itertools.count seeded from the current millisecond timestamp guarantees a globally unique index per request within the process.


4. start_candles_one_streamfollow_candle spam (closes #86)

follow_candle() (a WebSocket subscribe request) was called inside the 0.2 s polling loop, sending up to 100 subscribe messages for a single stream setup. This directly contradicts the README warning about excessive requests causing bans.

Fix: move the single subscribe call before the loop; the loop body now only polls candle_generated_check and sleeps.


Files Changed

File Change
pyquotex/stable_api.py 4 targeted fixes, +29 / -9 lines

Impact / Risks

  • No public API change — all method signatures and return types are unchanged
  • All fixes are confined to stable_api.py
  • Low risk — each change is a minimal, well-scoped correction
  • Consistent with patterns already established in the codebase (sentinel-before-send, event-driven wait)

AKIB473 added 2 commits May 1, 2026 18:20
…get_historical_candles and start_candles_one_stream

--- open_pending while/else (closes cleitonleonel#83) ---
The while/else construct caused instruments_follow() to fire on the
failure path (loop condition False == pending_id never set) and be
silently skipped on the success path. Replace with an explicit
if-check after the loop so instruments_follow is called only when
pending_id is actually present.

--- sell_option race condition (closes cleitonleonel#84) ---
sold_options_respond was reset to None AFTER the WebSocket request
was sent. On fast connections the response can arrive before the
reset, wiping the data and causing a guaranteed TimeoutError.
Move the sentinel reset to before the send, consistent with how
candles_data and other sentinels are handled in the codebase.

--- get_historical_candles worker index collision (closes cleitonleonel#85) ---
Each worker generated its request index as
  int(time.time() * 1000) + worker_id
Two workers (or two iterations within the same worker) executing in
the same millisecond produce identical indices. The event registry
keys responses on this index, so workers steal each other's data
silently. Replace with a module-level monotonic itertools.count
seeded from the current millisecond so every request gets a
globally unique index within the process.

--- start_candles_one_stream follow_candle spam (closes cleitonleonel#86) ---
follow_candle() (a WebSocket subscribe request) was called on every
0.2 s poll iteration, sending up to 100 subscribe messages per
stream setup. Move the single subscribe call to before the loop;
the loop body now only polls candle_generated_check and sleeps.
…te API reference

app.py — 27 commands now cover every public stable_api method:

Connection & Account (6)
  login, balance, server-time, set-demo-balance, settings, test-all

Assets & Payouts (3)
  assets, payout, payout-asset

Candle & Market Data (8)
  candles, candles-v2, candles-deep, history-line, candle-info,
  realtime-price, realtime-sentiment, realtime-candle

Trading (6)
  buy, sell, pending, check, result, signals

History & Indicators (2)
  history, indicator

Monitoring & Strategy (2)
  monitor, strategy

New commands not previously in app.py:
  server-time      → get_server_time()
  set-demo-balance → edit_practice_balance()
  settings         → store_settings_apply()
  payout           → get_payment()
  payout-asset     → get_payout_by_asset()
  candles-v2       → get_candle_v2()
  history-line     → get_history_line()
  candle-info      → opening_closing_current_candle()
  realtime-price   → start_realtime_price() + get_realtime_price()
  realtime-sentiment → start_realtime_sentiment() + get_realtime_sentiment()
  realtime-candle  → start_realtime_candle()
  pending          → open_pending()
  result           → get_result()
  signals          → start_signals_data() + get_signal_data()
  indicator        → calculate_indicator()

Other improvements:
  - Shared _add_account_flags / _add_asset_flag parser helpers (DRY)
  - _print_candles_table() shared table renderer for all candle commands
  - _save_candles_csv() for candles-deep --output flag
  - connect_with_retry() reused across all commands
  - COMMAND_MAP dict dispatch (replaces if/elif chain)
  - --demo/--live are now mutually exclusive groups

docs/en/API_REFERENCE.md — new complete API reference:
  - All 52 public methods documented with signatures, params, return types
  - CLI command table (27 commands)
  - Error handling guide
  - 8 complete usage examples (connect, RSI, trade+check, deep history,
    live indicator, pending order, asset scan, CSV export)
@cleitonleonel cleitonleonel merged commit 77e4bc2 into cleitonleonel:master May 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment