Skip to content

ThetaPrime v0.35.24

Latest

Choose a tag to compare

@dsharma01 dsharma01 released this 29 Aug 06:12
· 3 commits to main since this release

Download ThetaPrime-0.35.24-setup.exe

Fixed

Bug hunt area 8 — frontend (templates/static), 14 findings.

  • index.html: dashboard card no longer drops _long_active P&L; two leg-symbol XSS gaps (state card, Mark Exited form) escaped; January weekly-expiry date-parsing collision in _symbolExpiry() fixed.
  • composer.js: chipLabel() XSS on an untrusted spec.json block type escaped; draft-restore no longer loses its dirty/clean savedSnapshot baseline.
  • multi_sim_results.html: renderRow()/renderSessionMeta() unescaped fields fixed; stopSim() now surfaces failure feedback instead of failing silently.
  • performance.html: JSON.stringify-in-onclick apostrophe breakout fixed across 8 sites; eventBadge() event type escaped.
  • option_scanner.html: underlyings/expiries escaped into <option> values.
  • docs.html: DOMPurify added around marked.parse() output (defense-in-depth — the /docs/<path> route is already locked to docs/*.md via regex).
  • reports.html: falsy-zero strikes check fixed; dead row-highlight code fixed (wrong operator precedence + missing classList.add).

Validated archive/CODE_REVIEW_TEMPLATES_STATIC_2026-08-28.md — 6/8 confirmed and folded into the above, 2 stale-loading-UI findings deferred as refactor-scoped.

Bug hunt area 9 — performance.py, 2 findings.

  • add_event()/today_pnl(): trade dates were derived from the machine's local clock instead of IST, so a host not set to IST could attribute an exit to the wrong trading day. Now uses a fixed IST offset, matching holidays.py's _IST pattern.
  • get_summary(): a strategy/mode bucket was created for any matching row before checking REALIZED_EVENT_TYPES, so a strategy with only entry/monitor/skip events rendered as a phantom "0 trades" summary card. Bucket creation is now gated on a realized exit event.
  • Also hardened today_pnl()/get_summary() against malformed JSON rows so a damaged row is skipped instead of 500ing the dashboard.

Validated archive/CODE_REVIEW_PERFORMANCE_2026-08-28.md, 2/2 confirmed and fixed.

Bug hunt area 10 — runner.py/engine.py/reconciliation.py, 3 findings.

  • engine.py _close_and_record(): a partial/unconfirmed close was booked as a finalized full-quantity exit with no way to later correct the historical _closed record. Now tagged unconfirmed_legs, and a new _correct_closed_record() helper lets _retry_unconfirmed_close()'s per-leg true-up patch that record directly.
  • _execute_long_roll() (and sibling _execute_roll(), found in passing): a roll's replacement leg was dropped entirely when its fill never confirmed, leaving it untracked by reconciliation. Now kept tagged unconfirmed/order_id so reconciliation.py's existing fold-in picks it up automatically.
  • runner.py: the TRADE_EVENT parsing loop aborted on the first malformed line, silently dropping every later valid event. Moved to per-line try/except, log-and-continue.

Validated archive/CODE_REVIEW_RUNNER_ENGINE_RECONCILIATION_2026-08-28.md, 3/3 confirmed and fixed. reconciliation.py itself had no additional defect.


[0.35.23] — 2026-08-28

Fixed

Bug hunt area 4 — notifications/AI/MCP, 5 findings.

  • notifier.py send_notification(): the dedup cache no longer suppresses a retry after an all-channels-failed outage — the placeholder is popped if nothing actually sent, instead of blocking the same alert for the rest of the 10-minute dedup window.
  • notifier.py format_trade_event(): malformed/legacy numeric fields (rs, total_rs, per-leg entry/exit) no longer raise ValueError and drop the whole notification — added _safe_float() alongside the existing _sign/_is_negative fallback.
  • notifier.py desktop channel: get_desktop_alerts() and _send_desktop() now share one lock, fixing a RuntimeError: deque mutated during iteration under concurrent traffic.
  • mcp_server.py: THETAPRIME_MCP_PASSWORD is no longer sent over plaintext http:// to a non-loopback host — a non-loopback THETAPRIME_URL now requires https:// or the server refuses to start.
  • ai.py ask(): the bring-your-own base_url (custom OpenAI-compatible endpoint) now rejects non-http(s) schemes and the cloud metadata IP, closing an SSRF path that could forward the stored AI key.

Bug hunt area 5 — strategy components & composer, 2 findings.

  • strategies/components/exits.py: PremiumPctTarget and StopLossPctPremium's duplicated net-premium calculation deduplicated into a shared _net_premium(ctx) helper.
  • strategies/spec.py estimate_min_capital(): a covered-call holdings-lookup failure (broker offline, rate limit) now logs a warning instead of silently falling back to covered_qty=0, which looked identical to "no shares actually held" and quietly inflated the margin estimate.

Bug hunt area 6 — simulation & backtesting, 6 findings.

  • sim_engine.py / strategies/nifty_strangle_live.py: a user-requested sim stop no longer still runs run_eod() and books P&L for the aborted day; custom .py strategies (nifty_strangle_live.py, lib_v1_trade.py, Lib_v1_1_lot.py) can now actually be interrupted via a new sim_engine.should_stop().
  • multi_sim.py: the declared-but-unused _multi_sim_lock is now acquired in start()/extend(), closing a race where two concurrent requests could launch overlapping sessions against the same strategy's carry-forward state file.
  • multi_sim._run_extend(): a stop landing right as the prepend batch finishes no longer skips recompute_cumulative(), which left every pre-existing day's cumulative_pts stale.
  • sim_sessions.compute_summary(): a same-day-opened, still-open position at EOD no longer vanishes from every summary bucket (was excluded from trades, no_trades, and errors alike).
  • sim_engine.py: a non-positive STRATEGY_MONITOR_INTERVAL now raises ValueError up front instead of spinning the tick loop forever.
  • broker/__init__.py get_broker(): singleton check/rebuild/assign is now guarded by a lock (found while fixing the sim-engine reset call), via a new broker.reset_singleton().

Bug hunt area 7 — app.py routes, 11 findings.

  • square_off(): a close order that failed to place or didn't confirm no longer marks the position CLOSED — unconfirmed legs stay tracked as open under manual_control instead of silently dropping real exposure from monitoring/reconciliation.
  • square_off(): SHORT_CLOSED full-open-leg derivation now reads _long_active['legs'] instead of the never-pruned top-level legs dict, matching manual_exit's existing fix for the same bug class.
  • square_off() / edit_trade_state(): three falsy-zero bugs fixed (ltp_map.get(...) or entry_price, ref_price or None, average_price or entry_price) — a genuine 0.0 LTP/reference/average-price is no longer treated as missing.
  • Daily loss limit can no longer be silently disabled by a NaN/inf/-inf value — _parse_optional_float() now rejects non-finite input.
  • Auth/OAuth callback errors (Kite, Breeze, Dhan — 8 call sites) now route through _safe_error_message() instead of leaking raw exception text into flash/redirect messages.
  • Scheduled-backup time validation now range-checks hour/minute instead of accepting shape-only garbage like 99:99.
  • /api/desktop_alerts's loopback auth bypass now checks proxy headers like its sibling dev-only bypass, closing a same-host-proxy P&L/alert content leak.
  • trade_reset() now takes the same .closelock every other live-state route holds, preventing an interleaved read-then-delete from dropping a just-written position.
  • multi_sim.start()/extend() now return -1 (not 0) when blocked by an already-active session, so the UI reports "already running" instead of a misleading "no trading days found".
  • /ai/prompt/reports no longer 500s on an unhandled AttributeError when reports_data() hits its own exception branch.