ThetaPrime v0.35.8
Download ThetaPrime-0.35.8-setup.exe
Changelog
All notable changes to ThetaPrime are documented here.
Format follows Keep a Changelog.
Versioning follows Semantic Versioning.
Unreleased
[0.35.8] — 2026-08-25
Fixed
BreezeAdapter.margins()readunallocated_balance(unsegmented cash outside any allocation, confirmed near-empty live), not the F&O segment this adapter actually trades. Now usesallocated_fno/block_by_trade_fno.strategy_manager.clone_strategy()didn't patchdisplay_nameinside.spec.jsonfiles — Composer reads it from the spec itself, so a cloned strategy's name reverted when reopened for editing (#443).
[0.35.7] — 2026-08-24
Fixed
- A virtually-untraded deep ITM leg's best bid/ask could itself be mispriced — confirmed live 2026-08-24: NIFTY26AUG25950PE (1 DTE) had its MARKET close rejected ("no trades in this instrument") and the depth-based LIMIT fallback crossed into a thin, mispriced bid ~80pts below the leg's own intrinsic value.
_marketable_limit_pricenow clamps toward intrinsic (a SELL never goes below it, a BUY never above), threaded throughplace_order/convert_to_limitacross Kite/Breeze/Dhan adapters andstrategies/engine.py's close/retry path. Unconfirmed closes now stash the full leg so the retry can re-place it the same way a fresh close would.
[0.35.6] — 2026-08-24
Fixed
- Desktop shell (
installer/shell.py) opened its WebView2 window in private mode by default, so broker OAuth login never saved passwords/autofill/cookies across launches. Now uses a persistent per-user profile under%LOCALAPPDATA%.
[0.35.5] — 2026-08-23
Fixed
setup.issnever declared 64-bit install mode, so Inno Setup defaulted to 32-bit and installed intoProgram Files (x86)despite ThetaPrime being a 64-bit app. AddedArchitecturesAllowed/ArchitecturesInstallIn64BitModex64, plus detection and a migration prompt for a pre-fix (x86) install.
Changed
- Install docs (
docs/installation.md,docs/dev/installation.md) now describe broker-agnostic prerequisites (Kite/Breeze/Dhan) instead of Kite-only, and point at the correct app log path (C:\ProgramData\ThetaPrime\logs\thetaprime.log). - Dashboard's no-token banner now reads "Broker session expired" instead of Kite-specific wording.
[0.35.4] — 2026-08-23
Changed
- Install docs (
docs/installation.md,docs/setup.md,docs/security.md,docs/dev/installation.md) rewritten for the pywebview app-window/tray-icon flow, replacing stale browser-tab instructions; documents the license request flow and the NSSM service-registration failure mode. tools/create_coupon.py,tools/generate_license.py,tools/process_license_requests.pynow share atools/_wrangler.pyd1_execute()helper instead of each callingsubprocess.run()directly.
Added
installer/build_installation_pdf.pyregenerates the installation guide PDF.
[0.35.3] — 2026-08-22
Fixed
- Installer upgrades could fail with
DeleteFile failed; code 5 (Access is denied)on a bundled.pydfile —ThetaPrimeShell.exehides to the tray on window close rather than exiting, so it's almost always still running (with its own DLLs loaded) during an upgrade, and the installer only ever stopped the NSSM service.setup.issnow alsotaskkill /F /IM ThetaPrimeShell.exe(best-effort) before file replacement.
[0.35.2] — 2026-08-22
Fixed
- Installer's icon-cache refresh used
ie4uinit.exe -ClearIconCache, which isn't reliably present on all Windows builds (CreateProcess failed; code 2on a real install). Replaced with a directSHChangeNotify(SHCNE_ASSOCCHANGED, ...)call toshell32.dllinsetup.iss's[Code]section — no external binary to go missing.
[0.35.1] — 2026-08-22
Fixed
- Desktop shell (
ThetaPrimeShell.exe) showed a stale/incorrect icon after install — icon is now embedded into the exe at Nuitka compile time instead of referenced separately by the shortcut, and the installer clears Explorer's icon cache on upgrade so a changed embedded icon isn't hidden behind a stale cached one. shell.pynow passes the window icon to pywebview explicitly instead of relying on it being extracted from the running exe.
[0.35.0] — 2026-08-22
Added
- Desktop shell (
installer/shell.py): pywebview native window wrapping the dashboard, pystray tray icon, and win11toast toast notifications, replacing the old "open dashboard in browser" flow. notifier.pydesktop channel: queues alerts for the shell to poll viaGET /api/desktop_alerts, since the notifier can run inside the Session-0 NSSM service where UI can't be shown directly.- Installer now silently installs the WebView2 runtime if missing, and launches
ThetaPrimeShell.exefrom the Start Menu / post-install instead ofopen_dashboard.bat.
[0.34.2] — 2026-08-22
Added
docs/option-scanner.md— scan modes, results columns, Flow (OI Analysis) buildup categorization, and the Ask AI handoff, cross-linked fromdocs/strategy-usage.md.
Fixed
- Option Scanner page description said "Instruments & spot always from Kite" — stale since the broker-parity work made every field (instruments/spot/OI/LTPs) come from whichever broker is selected.
- 4 markdown tables in
docs/strategy-composer.md's regime-skip filter section were rendering as raw pipe-delimited text instead of tables (missing blank line before the table, same bug class as the list-gluing fix in v0.32.13).
Changed
marketing/features.md's Option Scanner entry updated for Flow (OI Analysis), multi-broker support, and Ask AI — was still describing only the original premium%/IV/delta scan.README.mdrewritten — was still describing the pre-multi-broker prototype (wrong product name, in-memory token storage, a PyInstaller build script that no longer exists).
[0.34.1] — 2026-08-22
Added
tests/test_broker_conformance.py— structural conformance suite that checks every broker adapter (Kite, Breeze, Dhan) implements the fullBrokerClientcontract and constants.docs/brokers/adding-a-new-broker.md— checklist for wiring in a 4th broker adapter.
Changed
- Broker adapters (Kite/Breeze/Dhan) and
scanner.py/app.pybrought into parity per the new conformance checklist.
[0.34.0] — 2026-08-21
Added
- Option Scanner: OI-buildup / unusual-volume / PCR flow analysis (Long/Short Buildup, Short Covering, Long Unwinding) per strike, plus an OI column on the plain scan. Previous-day OI baseline is fetched once per symbol per day and cached to
oi_baseline.json. - Option Scanner results have an "Ask AI" handoff — reviews the scan CSV for notable strikes, mispricings, or unusual activity via the configured AI provider.
option_scannerfeature flag now defaults on.- New
strategies/reliance_cash_secured_putstrategy (spec + generated .py).
Changed
- AI block-catalog prompt trimmed to type/param essentials and compacted to non-pretty-printed JSON — was hitting Groq's free-tier 8000 TPM limit and leaving no room for the auto-correction retry.
- API error responses no longer leak raw exception text/paths to the browser — routed through a shared handler that logs the full exception server-side with a correlation id and returns a short generic message instead.
quote()now returns open interest (oi) across all three broker adapters (Kite passthrough, Breeze/Dhan unverified live).
Fixed
- Live-candle instrument-token lookups are now cached per symbol instead of re-resolved on every call.
[0.33.2] — 2026-08-19
Added
- Trade journal CSV (
lib_v1_paper_trade_journal.csv) is now encrypted at rest and in backups, same path asrun_history.db/performance.db(issue #370). - Cheap RSS/thread-count snapshot logged every reconcile cycle, piggybacked on the existing broker round-trip — no documented CPU/RAM footprint for the always-on service previously (issue #375).
- Installer's Finished page now checks whether the NSSM Windows service actually registered instead of assuming success, and reports clearly if it didn't.
Changed
docs/setup.mddocuments the broker read-call retry/backoff behavior (2 retries, 0.5s/1.5s delay, 5 consecutive failures raise a "broker degraded" alert).
[0.33.1] — 2026-08-19
Added
- Build with AI in the Strategy Composer is now an interactive in-app chat when an AI provider is configured (Settings → AI): every reply is validated server-side before it reaches the UI, with one bounded auto-correction retry on an invalid spec. Falls back to the original copy/paste handoff when no provider is configured. Bring-your-own-key via any OpenAI-compatible endpoint (Groq default, Ollama for local/no-key use, OpenAI, or custom).
Fixed
- "Build with AI" occasionally producing an invalid spec with no feedback — replies are now run through the composer's own validator instead of trusted as-is.
- The AI prompt's block catalog dump cut from ~7960 to ~2960 tokens (dropped UI-only description/label text, compacted JSON) — was causing 413s and leaving no headroom for a retry within free-tier per-minute token limits.
[0.33.0] — 2026-08-19
Added
- Manual takeover:
square_off()accepts an optionallegs=[...]param to close a single position and leave the rest running (real order, not just a record). A strategy that's had a leg closed this way setsmanual_control— autopilot (entries, exits, adjustments, reentry) leaves it alone until every leg is closed, then resumes on its own. Strategy card and trade-status modal show a ✋ MANUAL badge; the manual-exit form gets a per-leg "Close Now" button.
Fixed
- Token-freshness nudges and broker session-dead notifications (Kite/Breeze/Dhan) no longer fire outside trading hours — gated to market hours ± 2h via new
holidays.in_reminder_window().
[0.32.17] — 2026-08-18
Added
- "External Data Sources" card on the Data Management page: last-updated status for the cached NFO instrument snapshot, NSE holiday calendar, and Yahoo-derived regime calibration. New
docs/data-sources.mddocuments each source. tools/replay_session.py: replays a historical volatile trading session through the real scheduler/monitor loop in paper mode with an optional broker-throttling burst injected, to verify ticks keep pace (issue #368).- Entry fills now warn (never abort) when a live fill lands more than 5% from its pre-trade reference price (
STRATEGY_MAX_SLIPPAGE_PCT).
Fixed
- A close order that never confirms filled is no longer silently dropped: the leg is flagged
_exit_unconfirmed, blocks new entries/re-entries until a human confirms via Mark Exited, is picked up immediately by reconciliation, and the trade notification now says "may still be open at the broker" instead of a plain ✅. - A disabled strategy with an open position now keeps running its monitor tick (PT/SL/EOD exits still fire) — only new entries are blocked, not the exits that protect an existing position.
run_entry()now takes an exclusive file lock so a cron tick and a manual/retry trigger landing in the same window can no longer both place orders.state.jsonwrites go through tmp+rename for atomicity, avoiding a truncated read by a concurrent dashboard poll.- Entry-rejection notifications report the actual per-leg broker status instead of a hardcoded "(margin?)" guess.
[0.32.16] — 2026-08-18
Added
- Custom 404/500 error pages (
templates/error.html); API routes now return a generic JSON error with a logged correlation id instead of leaking raw exception text/paths (_safe_error_message/_api_error, issue #366). - Live automation-latency metric:
SpecEnginetimes signal-detected → order-acknowledged for real trades (_mark_signal/_pop_latency);performance.latency_stats()computes p50/p95/max; Reports shows "Latency p50/p95 (live)" summary cards, green/red at the 200ms target.
Fixed
broker/regime.py: today's cached regime bar is no longer frozen, so intraday regime reads stay live instead of going stale until the next day's fetch.
Changed
live_candles: resolvedinstrument_tokenis now cached per symbol, avoiding a redundant lookup on every poll.
[0.32.15] — 2026-08-17
Added
long_eodexit:hold_below_ptsskips the pre-expiry long-leg close when P&L is already worse than the expiry-guaranteed floor. Left at 0, a bracket long-combo (long call struck below a long put) auto-derives that floor from strikes/entry prices; any other leg shape just honors the DTE trigger as before.
Fixed
long_eodctx now prices the long legs live instead of using the0.0pnl_ptsdefault — needed forhold_below_pts(and any leg-scoped PT/SL) to evaluate correctly.
[0.32.14] — 2026-08-17
Added
KiteAdapter: session-dead alerting and a degraded-streak warning, ported from the Breeze/Dhan adapters — Kite was the one adapter that could fail silently on a dead token or a flaky run of 5xx errors.- Pre-market token-freshness check (08:45 IST): notifies once per trading day if a broker with an enabled real-money strategy still has a stale/missing session token, instead of the first sign being a failed entry at market open.
- Scheduler self-heal: detects and restarts a silently-dead APScheduler background thread (confirmed real — one ran dead for 3 days with no error logged) and notifies when it happens.
- Coupon tool / license server: coupons can now be bound to a single email (
--email), so paid-sale codes aren't redeemable by anyone; beta/promo codes stay open by omitting it.
Removed
skipnotifications (filter-blocked entries) — noisy, not actionable per-event.
[0.32.13] — 2026-08-15
Fixed
- Markdown lists in several
docs/*.mdfiles were glued into the preceding paragraph instead of rendering as a list — CommonMark requires a blank line before a list to parse it as one. Fixed across 10 doc files; also switchedcomposer-strategy-examples.md's numbered items from- 1. Footo plain1. Fooso the embedded number isn't parsed as a nested list. PremiumTargetleg selector: failure message now distinguishes "no live/BS price at all" from "priced but none within the configured band" instead of one generic error.
Changed
docs/strategy-usage.md: added a broker-selection comparison table for the Simulate modal (Breeze/Kite/Dhan data retention and synthetic-price caveats).
[0.32.12] — 2026-08-14
Fixed
- Entry rejection handling could unwind live positions that were never actually rejected: an async accept-then-reject race in Kite's RMS could leave an order reading REJECTED in the order book while the position had actually filled.
_cancel_and_rollbacknow cross-checksbroker.positions()before trusting a REJECTED verdict, and no longer auto-closes long legs (LC/LP) when only short siblings are rejected — those are parked into_long_activeinstead.
Added
edit_trade_state:confirm_active— manual, broker-verified escape hatch to move aPARTIAL_ENTRY_FAILEDtrade's rejected legs intoACTIVE, for state predating the fix above.
[0.32.11] — 2026-08-14
Added
- Docs viewer: sidebar search across doc titles and body content, auto-built on-page table of contents with scroll-spy, prev/next doc footer, mobile off-canvas sidebar.
Changed
- Flat-card visual pass: dropped
shadow-sm/shadowacross dashboard, auth, reports, performance, scanner, history, and onboarding templates. - Login/license splash: navy background matching the DESIGN.md palette,
prefers-reduced-motionsupport, responsive width on small screens. - DESIGN.md: added Hero and docs-reading-scale typography tokens, documented accepted one-off utility values.
- Accessibility: skip-to-content link and
<main>landmark on every page, hidden dashboard h1.
Fixed
- Windows installer build:
yfinancewasn't bundled into the Nuitka build (broker/is nofollow'd, so the static import scan misses its lazy import), causing installed builds to fail on India VIX lookups.
[0.32.10] — 2026-08-14
Added
roll_long_legsforroll_untested: opt-in delta-drift rolling of held diagonal/calendar long legs (LC/LP), independent of the short-leg roll — a long hedge leg has no decay/pressure signal, so it rolls on live delta drifting pastlong_target_delta±long_delta_bandinstead. Short-leg roll widened to keep working across short re-entry cycles instead of stopping once a long leg was parked.delta_otmleg selector: OTM strike closest to a target|delta|, scanned by %-of-spot, no time-value constraint.stop_loss_pct_premiumexit: stop loss at -N% of entry premium (credit or debit), adapting across volatility regimes better than a fixed points stop.- Composer UI: reorder blocks within a slot (move up/down); payoff-diagram compute errors now shown inline instead of silently hiding the chart.
Fixed
- "Long legs held" status message always reported
DTE=1regardless of the configuredlong_eodtrigger.
Changed
long_eoddtetrigger range widened from 1–7 to 1–60.
[0.32.9] — 2026-08-13
Fixed
- Legs with no live tick now refresh via Black-Scholes fair value (
_reprice_bs) instead of sitting frozen at a stale price — previously showed flat +0.00 pts P&L all session. - Day-rollover state reset (P&L, closed-trade log, re-entry tracking) factored into
_reset_if_new_day()and called from bothrun_entry()andrun_monitor(), so a crashed/skipped entry job no longer leaves_short_closed_todaystuck from the prior day and permanently blocking re-entry.
[0.32.8] — 2026-08-13
Added
roll_short_legsfor Diagonal/Calendar (default on): re-sells the short leg(s) each time they close while the long leg(s) stay held, instead of closing once and sitting out until the long leg's own exit. Configurable via the composer's generic param form.- Dhan: expired option contracts now fall back to the
rollingoptionendpoint for historical data (spot-relative strike/expiry slot) when the instrument master has dropped the exact contract, matching Kite's expired-contract limitation. Fallback candles are taggedapproxand flagged in sim-availability messaging/hints as indicative, not exact fill history.
Fixed
- Multi-sim day parsing (
multi_sim.py) summed only the lastshort_exit/exitevent per day, silently dropping P&L from earlier same-day rolls underroll_short_legs— now sums every exit event seen that day. - NSSM service stop/remove (
installer/nssm_setup.bat) could hang the installer indefinitely if the app didn't shut down cleanly; stop is now bounded to 15s before force-killing the backing process, with fast-path exits for a not-installed or already-stopped service.
Changed
dhanenabled by default inconfig.json.
[0.32.7] — 2026-08-12
Added
- Multi-day sim extend/resume: widen a completed or stopped multi-day sim session with new days before its start date, after its end date, or both, without re-running already-stored days. Forward extension restores the last day's saved state snapshot before continuing; backward extension runs clean and stops at the existing start date without overwriting stored days. A stitch-risk banner and per-day badge flag when a carry-overnight position spans the join.
[0.32.6] — 2026-08-11
Fixed
- SIMULATION COMPLETE summary no longer buckets 100% of a combined single-phase close (e.g. iron condor's LC/LP + SC/SP closed together) as Short P&L — split is now computed per-leg instead of by an entry-level
long_exitflag, soLong P&Lno longer always prints+0.00for these structures. storage.pyretries file removal onPermissionError— OneDrive can briefly hold an exclusive lock on a just-written file while syncing.
Changed
- Clone-strategy dialog shows the correct
.spec.jsonextension label for spec-based strategies instead of always.py.
[0.32.5] — 2026-08-11
Fixed
- Reports/summary P&L no longer double-counts a diagonal's long-leg close (
long_exit) against a same-dayshort_exit—long_exit's totals fold in the day's short-leg P&L, which was being summed a second time. Addedperformance.realized_pnl()to pick the right field per event type.
[0.32.4] — 2026-08-11
Removed
- One-off
defect-review-report.md/.pdfandgenerate_pdf_report.pythat were accidentally tracked in 0.32.3 — internal release artifacts, not app functionality.
[0.32.3] — 2026-08-11
Added
- Trade data encryption at rest:
run_history.dbandperformance.dbare now encrypted (AES-256-GCM) whenever ThetaPrime isn't running. The key lives in Windows Credential Manager alongside broker tokens, so scheduled/automated trading survives a reboot unattended. A one-time recovery code is shown on the Auth page on first run after upgrading, and can be regenerated from the Data Encryption card. - Scheduled backups: a new section under Backup & Restore runs the existing backup automatically on a daily timer to any local folder (including one synced by OneDrive/Dropbox/Google Drive), with configurable retention. Trade data is encrypted inside the backup zip; strategy files/settings are not.
Changed
- Legacy
strategy_builder.html/strategy_generator.pymoved toarchive/— superseded by the composer/spec-only strategy authoring path.
[0.32.2] — 2026-08-10
Fixed
_leg_candles()now falls back to the cached NFO instrument-token dump whenquote()misses a token — same fallback chain asoption_history_api(confirmed 2026-08-10: Option History resolved a tokenlive_candlesreported as missing).- Manual square-off now records the closed position into
state['_closed'], closing a gap whereengine.run_entry()'s "already closed today" guard didn't fire — a manual square-off could be followed by an unwanted second entry the same day (confirmed in prod 2026-08-07). broker/regime.py: NIFTY/VIX series are now aligned by date before the volatility vote walk (an unmatched bar from either ticker was silently shifting indices out of sync). A "medium" hold that outlives_STALE_CAPsessions with no reconfirmation now decays to a plurality read instead of riding a stale label indefinitely.
[0.32.1] — 2026-08-10
Fixed
live_candles's_leg_candles()swallowed exceptions from the Kite instrument-token lookup (quote()) with no log line, unlike the sibling_fetch()helper and the matching pattern insim_availability.py/sim_ltp.py. A leg whose candle chart silently came up empty (e.g. "No candle data today for LP") left no trace of why. Now logged.
[0.32.0] — 2026-08-09
Added
- Standard/Pro license tiers: license keys now sign a broker-count tier (
expiry|tier|signature) — Standard allows one broker enabled at a time across all strategies, Pro allows as many as you've connected (Kite, Breeze, Dhan). Enforced at both strategy-enable time and at launch time, so a stopped-app metadata edit can't bypass it. Legacy keys issued before this change keep working, grandfathered as Pro.tools/generate_license.py --tier. - The 15-day free trial now grants full Pro-equivalent access (live trading, every broker) for its entire duration — the tier you purchase only starts to matter once the trial ends.
Changed
/license: activating a lower-tier key that would exceed your currently-enabled broker count now warns (naming the affected strategy) instead of silently letting it happen — activation itself still always succeeds.
[0.31.0] — 2026-08-09
Added
- In-app license purchase requests:
/licensenow has a "Request Standard" / "Request Pro" form — email + optional coupon code, machine fingerprint included automatically (no more copy-pasting it into an email). A valid coupon approves instantly; otherwise the request queues for manual review. The page auto-fills the License Key field once a request is fulfilled, ready to Activate. - New Cloudflare Worker routes (
license-server/src/index.js) and D1 tables (license_requests,coupons) backing the above — atomic coupon redemption, a UUID+email dual-check on status lookups so a request ID alone can never pull back a signed key, and a per-fingerprint pending-request cap. tools/create_coupon.py(issue a coupon) andtools/process_license_requests.py(offline poller — signs keys for approved requests on a schedule; delivery is a manual email-these list for now, seeplans/plan-license-purchase-intake.md).
Planned, not yet shipped
plans/plan-standard-pro-tiers.mdwas revised to grant the 15-day trial full Pro-equivalent access (live trading, unlimited brokers) — not implemented in this release; the existingapp.pygate blocking Real-mode trading during trial is still active. Tracked for a future release.
[0.30.1] — 2026-08-08
Fixed
docs/strategy-usage.md: 3 broken TOC anchor links (#money-mode-paper--real,#config-lots--monitor-interval,#summary-cards-1) didn't match the actual heading IDs python-markdown generates, found by the website docs sync's anchor verifier.
[0.30.0] — 2026-08-08
Added
- Build with AI: new Composer button opens a modal where you describe a strategy in plain English; the app bundles that ask with the live block catalog, a worked example, and guardrails an AI can't infer on its own (NIFTY-only weekly expiry, F&O stock DTE floor,
coveredleg scope, VIX filter scope) into a prompt, hands it off to your chat AI (same flow as the existing Ask AI button), and you paste the reply into the existing Import spec dialog.ai_prompts.build_strategy_request_prompt()+POST /ai/prompt/build_strategy. Seeplans/plan-ai-strategy-builder-prompt.md. - Composer: Export button (download the current spec as
.spec.json) and an Advanced edit mode to hand-edit the spec JSON in place, both reusing the existing spec-normalization path. manual_exitnow accepts a partialpricesmap covering a subset of a trade's open legs — for when only some legs were manually closed at the broker. Closing every open leg still behaves as a full close (CLOSED); closing a subset parks the rest under_long_activeand moves status toSHORT_CLOSED, mirroring the two-phase transitionstrategies/engine.py's_close_legs()makes automatically when an exit condition fires on just the short legs.
Fixed
static/ai_handoff.js: a prompt longer than the URL length limit (or a clipboard-only provider like Gemini/Copilot) was still built into a?q=-style URL, opening a dead 414 tab — the catalog-sized Build with AI prompt hit this on every use. Now falls back to the provider's plain URL and copies the prompt to the clipboard instead.- Composer's Import spec dialog and new Advanced-edit box both now strip a wrapping
```json ... ```code fence before parsing — chat AIs wrap JSON replies in one by default even when told not to, so the first paste back from Build with AI was failing with a raw JSON parse error.
Changed
- Installer: setup exe now carries the app icon (
SetupIconFile); routine version upgrades (not the pre-licensing case) get a plain "Upgrading ThetaPrime — your data will be preserved" notice instead of no notice at all.
[0.29.0] — 2026-08-06
Added
- Real covered-call support: the
short_callstructure gained acoveredparam — when set,SpecEngine.run_entryrefuses to arm the position unless a live broker holdings lookup shows at least one lot's worth of the underlying already held (newBrokerClient.holdings(), implemented on all three adapters), andmargin_estimate.estimate_margin_from_legs/spec.estimate_min_capitalprice it as covered (no SPAN margin) instead of a naked short call once holdings confirm the cover. Composer template restored as Covered call (F&O stock, income — requires holding the lot already), alongside yesterday's honestly-renamed Short call (F&O stock, income — uncovered, no equity leg) for the naked variant. Seeplans/plan-covered-call-equity-leg.md. - Extended the same
coveredgate toshort_strangle/short_straddle— applies to the SC leg only (SP stays naked/cash-secured, unchanged). New Covered strangle (F&O stock, income — call side requires holding the lot) composer template.
Fixed
- Composer payoff diagram and min_capital estimate ignored the selected underlying for any F&O stock:
strategies/payoff.py'scompute_payofffell back to a static_DEFAULT_PARAMStable that happened to equal NIFTY's own lot_size/strike_step exactly, so switching e.g. NIFTY → RELIANCE silently kept using NIFTY's numbers. Now resolves the real lot_size/strike_step from the live instrument dump viastrategies/instruments.pyfor any underlying not in the static index table. Uncovered a second bug while fixing it:_parse_symbol's regex was hardcoded to a literal "NIFTY" prefix — harmless whilectx.symbol_prefixwas also always "NIFTY" regardless of underlying (self-consistent by accident), but would have broken every non-NIFTY payoff outright once the prefix bug was fixed; both are now correct together.static/composer.js's underlying dropdown also now refreshes the payoff diagram's spot field via/composer/spot_ltpon change (previously only on page load), so the stale-spot half of the same symptom is fixed too.
Changed
static/composer.js:stock_covered_calltemplate renamed tostock_short_call/ "Short call (uncovered)" — it sold a bareshort_callwith no equity leg, so payoff/Greeks/min_capital were computed as a naked short call despite the "Covered Call" label. No behavior change, label only.
[0.28.11] — 2026-08-05
Changed
docs/mcp-setup.md(ships to customers) trimmed to the installed-service MCP setup only. The running-from-source setup moved to newdocs/dev/mcp-setup.md(internal, stripped bybuild.ps1), matching the existingdocs/dev/pattern (installation.md,licensing-internal.md).
[0.28.10] — 2026-08-05
Fixed
installer/build.ps1wasn't copyingmcp_server.pyinto the installer output, so an installed customer had no file for Claude Desktop to run — added it alongsidestrategies//broker//config.json/version.py, the other plain-source files that ship next to the compiledapp.exe.docs/mcp-setup.mdonly documented running from source; added a separate "installed Windows service" setup path (port 5000, no dev-mode localhost auth bypass, needs a separately installed Python sinceapp.exe's bundled runtime isn't general-purpose).
[0.28.9] — 2026-08-04
Added
- MCP server (
mcp_server.py) exposing six read-only tools to AI assistants (Claude Desktop, Cursor) over local stdio:list_strategies,get_strategy_status,get_positions,get_pnl,get_reconciliation_status,get_recent_logs. Talks to the already-running app overlocalhostHTTP (new/mcp/strategies,/mcp/positionsroutes; reuses existing/api/performance/summary,/api/reconcile/status,/api/events), authenticated with the existing dashboard password via anX-MCP-Authheader./api/performance/summarygained optionalstrategy/mode/from_date/to_datefilters. Seedocs/mcp-setup.md. (#263)
[0.28.8] — 2026-08-03
Fixed
strategies/instruments.py(framework code for multi-instrument support) was missing from the installer's framework allowlist (build.ps1's$STRATEGY_INFRA_FILES/strategy_manager.py's_INFRA_FILES), so the build stripped it as a customer strategy instance and the compiled build crashed on startup withImportError: cannot import name 'instruments' from 'strategies'.
[0.28.7] — 2026-08-03
Fixed
scanner.pyimported Black-Scholes helpers fromstrategies.lib_v1_trade, a legacy strategy instance the installer build strips from customer installs. Repointed tostrategies/components/blackscholes.py(the framework copy shipped since 2026-07-27), which fixes a startup crash (ModuleNotFoundError) in the compiled build.
[0.28.6] — 2026-08-03
Added
- Multi-instrument support: spec strategies can now run on BankNifty, FinNifty and stocks, not just NIFTY. Lot sizes and strike ladders are resolved from the live NFO instrument dump (
strategies/instruments.py) instead of a hand-maintained table — the exchange revises these periodically and a stale hardcoded value would place a real order on the wrong contract. Reuses scanner's existing 4h instrument cache; NIFTY keeps a hardcoded fast path so engine construction needs no network round trip in the common case. Composer UI, leg selectors, filters, exits and spec validation all updated for per-instrument strike steps. sim_availability.py: pre-flight probe that samples broker historical-data coverage (one daily-candle call on the underlying + ATM option minute probes on 3 sampled days) before a multi-day simulation burns ~180s on a date the broker has no candles for. Deliberately a sample, not a guarantee.- New sample specs: BankNifty 9:20 ATM straddle, BankNifty diagonal iron butterfly, bear put spread.
- Playwright-based composer end-to-end test (
tests/test_composer_e2e.py; runplaywright install chromiumonce).
Changed
- One-off import/check scripts moved to
archive/.
[0.28.5] — 2026-07-31
Added
- Data-driven regime calibration: regime classifier thresholds (realized-vol, VIX delta, USDINR, crude, VIX-RV spread cutoffs) are now derived yearly from historical data via
broker/regime_calibrate.pyand loaded fromregime_calibration.json, replacing hardcoded cutoffs. The FII/DII net-flow risk-appetite signal (NSE endpoint had no date param, blocking backtests) is replaced with a VIX-RV spread proxy. - Notifier now formats composer-engine multi-leg exits and long-leg exits (previously only the strangle and LIB V1 shapes were handled), with a new
long_exitnotify event. - New regime API endpoint.
[0.28.4] — 2026-07-31
Added
- Telegram notification channel: alerts (entries, exits, adjustments) can now be sent directly to a Telegram chat via the Bot API, alongside the existing email and webhook channels — no bridge process required. Configure a bot token + chat ID in the Notifications card, with a Send Test button. Notification setup docs (email, Telegram, webhook, trigger events, troubleshooting) moved into
docs/setup.md.
[0.28.3] — 2026-07-30
Fixed
- Composer strategies with a
time_windowfilter later than 09:45 could never enter: the scheduler's one-shot entry job always fired at 09:45 regardless of the spec's own filter (composer sidecars never expose anentry_timeparam), and monitor mode never retried entry — any transient miss silently forfeited the whole day. The entry job now derives its fire time from the spec'stime_windowfilter when set;run_monitor()now retries entry on every tick while no position is open;run_entry()gained a guard against re-entering after the day's trade has already closed (reentries exhausted / max daily loss hit), so the new retry path can't restart trading past that limit. Also fixesnifty_strangle_adjustment_12jul2026andrent_collector_condor_dev, which had the same latent bug. - Webhook notification failures are now logged as an app event (
notify.whatsapp_failed) instead of only a log line, so a broken WhatsApp webhook is visible in the Event Log.
[0.28.2] — 2026-07-30
Fixed
- Diagonal strategies could get stuck refusing to re-enter after a long-leg EOD close:
_close_longfinalized the trade toCLOSEDwithout clearing_active, so if short legs (SC/SP) were still tracked there when the long legs closed, they stayed marked "open" in state forever — blocking every future entry with "Position already open" even though nothing was actually open at the broker (root cause of the 2026-07-27 DIB incident).
[0.28.1] — 2026-07-29
Changed
- Strategy card controls redesigned: the 9-item ⋮ dropdown (Custom Schedule, Real-Time, separate Arm/Disarm) is replaced by two card-face controls — an Automation switch that directly creates/removes the strategy's scheduled entry+monitor jobs, and a Paper/Real toggle where switching to Real is now the arm action itself, no separate arm step. Turning Automation off or switching Real→Paper while a Real position is open now asks for confirmation first, since it stops exit monitoring.
- Reports page adds an Active / Archived / All status filter (applies when no single strategy is selected) so a retired strategy's trade history stays reviewable after archiving.
- Performance page's Paper/Real mode badges now use the same solid-fill contrast as the dashboard's toggle, instead of a washed-out tonal style.
Removed
- Custom Schedule (interval/daily cron trigger) is gone — every strategy now schedules via the Automation switch's fixed entry+monitor jobs.
[0.28.0] — 2026-07-29
Added
- Archive / Unarchive strategy: strategies can now be hidden from the active dashboard list without deleting them, via ⋮ → 🗄 Archive Strategy. Archived strategies live in a collapsed Archived panel with Restore and ✕ Delete Permanently actions. Archiving requires the strategy to be disabled with no open position first, and automatically disarms it and removes its scheduled jobs.
Fixed
- Dashboard and Performance pages showed strategy identity inconsistently: the Dashboard showed the display name with the filename hidden behind a dev-only flag, while the Performance page mostly showed the raw filename. Both pages now always show "Display Name (filename)" together. The Performance page's strategy-label lookup also now matches on the extension-stripped base name, so a trade event recorded under a slightly different identifier still resolves to the right display name instead of falling back to a bare, unlabeled string.
Changed
- Scheduler no longer logs a
strategy.skippedevent to the App Event Log every time a disabled strategy's job fires and no-ops — this was pure noise since disabling is already a deliberate, visible action.
[0.27.0] — 2026-07-28
Added
- First-run setup wizard: a fresh install (no broker credentials saved yet) now redirects to
/onboarding— a welcome screen leading into the existing Auth page (shown with a "Setup" banner and Continue →) and a final step offering to run a paper simulation on yesterday's data. Skippable at any point; never reappears once dismissed or completed. - Edit Trade State: a new action in the Trade Status modal for correcting lots/lot_size/expiry or a leg's symbol/action/strike/entry price/exit price after the fact — never opens or closes a position, only fixes bookkeeping, with a Validate vs Broker check and every change appended to the state file's
edit_log. - Manual Entry / Mark Exited now use a generic N-leg form (not hardcoded to SC/SP/LC/LP,
+ Add legfor anything beyond the default four) and a Validate vs Broker check that confirms each symbol is still tradable and flags a price far from the live LTP before saving. - Adjustment (strangle-roll) events now send a WhatsApp notification, and show up as their own event types (
adjustment/adjustment_failed) in the Performance event log filter and the multi-sim day log — the sim summary now also shows an Adjustments card (count, realized pts, failures). - Strategy Composer: a Simulate button appears once a strategy is saved, jumping straight into a paper sim for it.
Fixed
- Disabled strategies still showed a live next-run schedule: toggling a strategy off only flips its
enabledmetadata flag — the underlying APScheduler entry/monitor jobs stay registered (they correctly no-op when they fire) — but the dashboard card kept showing "Entry: Tomorrow 09:45 · Monitor: Tomorrow 10:00" regardless, implying the strategy would still trade. The schedule is now hidden on the card whenever the strategy is disabled. - manual_exit double-counted P&L for a
SHORT_CLOSEDposition's already-closed long legs: it read "still open" legs from the un-pruned top-levellegsdict instead of_long_active, adding a closed long leg's P&L on top of what was already carried forward.manual_exitand the new Edit Trade State now share one P&L recompute (_recompute_pnl) derived from actual leg prices. - Multi-day sim runs now report "no historical price data available for {symbols}" as its own error instead of folding it into a generic no-trade day, when the broker has no candles for an entry's symbols (e.g. an expired/unlisted contract).
sim_ltpno longer burns 3 retries + backoff per instrument when a contract has no resolvableinstrument_token(already expired/unlisted) — treated as no-data immediately instead of guaranteed-to-fail retries.- Simulated-date runs now compute gap filters' previous close from the correct prior trading day's daily candle, instead of the broker's live (today's) OHLC quote.
[0.26.11] — 2026-07-27
Fixed
- Scheduled Jobs showed live countdowns for disabled/unarmed strategies: the Scheduled Jobs list had no idea whether a strategy was enabled or armed, so a fully-off strategy still displayed active entry/monitor jobs. Each job row now shows "disabled — will not run" or "unarmed — will run as paper, not real" based on the strategy's actual state.
Added
- Manual entry/exit now warns in the response if the performance-log write fails, so a backfilled trade doesn't silently drop out of reports.
[0.26.10] — 2026-07-27
Added
- Manual entry / manual exit: when automation fails to fire an entry or exit and the position is placed or closed directly at the broker, a strategy's Trade Status modal now has Manual Entry and Mark Exited (Manual) actions to backfill ThetaPrime's own state file with the real leg details — neither ever places a broker order. Once saved, the position resumes normal monitoring (exits, adjustments, EOD handling) from the next scheduled run.
Fixed
- Diagonal long leg closing before the short leg: when a short leg's own exit and the long legs'
long_eodDTE=1 exit were both due on the same tick, the long leg closed first, stripping the short leg's margin hedge and leaving SC/SP open and uncovered for the rest of the day (the scheduler stops monitoring once the long-leg close sets status to CLOSED). The short leg now closes and is verified first; the long-leg close is deferred to the same tick, after.
[0.26.9] — 2026-07-26
Added
- Simulation Notes: multi-day sim sessions now have a free-text notes field for recording parameter-setting observations when reviewing past tests.
- Recorded strategy config: each sim session now snapshots the strategy's full config as it was at launch (composer spec blocks, or flat params for script strategies), shown as a collapsed, human-readable breakdown — not a raw JSON dump — in both the results page and Reports → Sim History, so later live edits to the strategy don't retroactively change what a historical report shows.
- Run time: multi-day sim sessions now record and display how long the run took, in both the session list and the detail view.
Changed
- Adjustment rolls now apply to any position with a short SC/SP pair, not just structures with exactly two legs (e.g. iron condor's long wings are left untouched by the roll instead of blocking it entirely).
- Default strategy entry time moved from 9:45am to 10:00am.
- Multi-Day Sim's "Start" button now shows a spinner while the run is being launched, instead of the modal closing instantly with no feedback.
[0.26.8] — 2026-07-26
Fixed
- 0.26.7 installer crash-looped on startup:
installer/build.ps1's strategy-instance allowlist (and itsstrategy_manager.pycounterpart) predatedstrategies/margin_estimate.py, so the build stripped it before packaging and every compiledapp.pyimport of it raisedModuleNotFoundErrorat startup. Added the file to both allowlists.
[0.26.7] — 2026-07-26
Added
min_capitalfield on strategy specs (issue #232): a capital-tier tag stored on the spec, settable manually or via a new Estimate button in the Composer next to the strategy name, displayed in Indian number format (click to edit the raw value). Validated byvalidate_spec().- Deterministic margin estimate (
strategies/margin_estimate.py) replaces the flat 20%-of-notional heuristic used whenever a real broker margin API isn't available (paper mode, Breeze, Dhan, or a Kitebasket_margin()failure): defined-risk spreads (condor/butterfly/vertical) are estimated from wing width minus net credit; undefined-risk positions (naked strangle/straddle) from a stress-move-of-spot heuristic. The same estimate powers the Composer'smin_capitalauto-estimate, reusing the payoff diagram's leg resolution.
Changed
- Live dashboard's "Capital" label renamed to "Notional Exposure" and centered on the strategy card, to avoid it reading as capital actually committed rather than notional position size.
Fixed
- Margin estimate for defined-risk spreads was ~10-15x too high: the old flat 20%-of-notional fallback ignored that a condor/butterfly/vertical's risk is capped by its wings, so it priced a defined-risk spread as if it carried naked-strangle-level risk.
[0.26.6] — 2026-07-25
Fixed
- Installer shipped the developer's own dev strategies to every customer:
installer/build.ps1copiedstrategies/wholesale, only stripping legacy raw.pyscripts — every composer.spec.jsoninstance (e.g.dib_30jun2026,nifty_strangle_live,rent_collector_condor_dev), their sidecars,metadata.json(real broker/lots/enabled settings), and a 13MB staleinstruments_cache/dump all reached the customer install. Now strips everything instrategies/except the framework files and the blankstrategy_template.py/.jsonpair, so a fresh install ships with zero pre-configured strategies.
[0.26.5] — 2026-07-25
Added
- Capital deployed / margin required per strategy on the live dashboard: each strategy card now shows notional capital deployed (spot × open quantity) alongside margin required — a real Kite basket-margin call (
KiteConnect.basket_order_margins(), spread-benefit-adjusted) for real-mode strategies, or a 20%-of-notional heuristic for paper mode and brokers without a margin-calculator API (Breeze, Dhan). Cards with no open position show a muted placeholder instead of an empty line. - Regime skip filter (
regime_skip): vetoes entry when the current market regime — NIFTY trend × India VIX volatility, gated by a USDINR/Crude/FII-DII risk-appetite read — matches a chosen regime. Broker-independent (reads Yahoo Finance + NSE's public endpoints instead ofhistorical_data()), so it works the same under Kite, Breeze, or Dhan. See Regime skip filter. - Rent Collector Condor template in the Strategy Composer's Load template dropdown: a weekly premium-target iron condor carried across days via Carry position instead of closed daily, aimed at capital-efficient multi-day theta collection. Documented in
docs/composer-strategy-examples.md(example 13). - Four more Composer templates, each putting a previously-unused block to work: Gamma-Fenced Daily Condor (0DTE iron condor with shorts placed at constant gamma via
gamma_target, backed by adelta_stop), Put Calendar Skew Harvester (put_calendar— the put-side mirror of the existing call calendar, harvesting NIFTY's put-skew richness), Self-Centering Condor (iron condor whose shorts roll on both the tested and untested side viaroll_untested'sside: both), and Monthly FD-Plus Condor (far-OTM monthly condor sized withcapital_pctinstead of fixed lots). Documented indocs/composer-strategy-examples.md(examples 14-17).
Fixed
- Multi-day sim silently discarded
carry_eodpositions:run_fullday()deleted its state file at the start of every simulated day, so a position acarry_eodblock left open overnight was wiped before the next day's subprocess even started — every day re-entered fresh and never realized any P&L, showing as a flatbreakevenfor the whole backtest.multi_sim.pynow carries state forward across days for the session it's running (day 1 still starts clean); a day where a carried position sits open with no entry/exit now shows a distinct Holding status instead of being mislabeled No Trade/Breakeven. diagonal_iron_flytemplate's long-leg exit was dead code: itsprofit_target/stop_loss/long_eodexits were never leg-scoped, so the short-leg/long-leg phase split (_long_active) never triggered andlong_eod's DTE=1 protection could never fire. Now scoped to["SC","SP"]and["LC","LP"]respectively.- Multi-sim reports understated win rate for carried positions:
compute_summary()'stradescount usedentry_taken, which the carry-state fix above (correctly) setsTrueon both a carried position's entry day (pnl=0) and its later exit day (real pnl) — doubling the trades denominator for every multi-day trade. Now countsexit_reason(set only the day a position actually closes) instead, so win rate reflects real trades, not calendar days touched. SpecEngine._load()/_save()could crash a sim day on a transient file lock: seen as a one-offPermissionErrorreading the state JSON mid-backtest (repo lives in a synced folder, which can briefly lock a file on write). Both now retry a couple times with a short backoff before giving up.
[0.26.4] — 2026-07-22
Fixed
- License fingerprint no longer flaps on restart:
get_fingerprint()now reads Windows' per-installMachineGuidinstead ofuuid.getnode()(MAC address). The old scheme was unstable on any machine with more than one network adapter (e.g. Wi-Fi + a VPN like Tailscale) — adapter enumeration order can race the VPN service coming up at boot, silently changing the fingerprint and invalidating an otherwise-valid license until the app was restarted again. Every key issued before this fix needs reissuing against the new MachineGuid-based fingerprint.
[0.26.3] — 2026-07-22
Added
- POP (Probability of Profit) on the Composer payoff panel: the payoff diagram now shows a Black-Scholes-estimated probability that the structure is profitable at expiry (India VIX as the IV proxy), alongside max profit/loss and breakevens. Computed by integrating the risk-neutral spot distribution over the existing payoff curve grid (
strategies/payoff.py), so it works for any leg shape, not just single-breakeven strangles. Also included in the Composer's Ask AI prompt. - Option Greeks section on the Composer payoff panel: a dedicated section below the chart shows the net position's Delta/Gamma/Theta/Vega (word + symbol, Black-Scholes estimates at entry, per lot).
- P&L what-if table: below the payoff chart, a small table shows expiry and mid-trade P&L at spot ±1/2/3%.
- Gamma target leg selector: new
gamma_targetblock scans an OTM% band for the strike whose Black-Scholes gamma is closest to a target — an alternative to OTM %/premium/delta targeting for strategies that care about rate-of-change of delta.
[0.26.2] — 2026-07-22
Changed
- Production server swapped from Flask's dev server to waitress:
app.run()was never meant for production (single-threaded, no real concurrency);waitress.serve(app, threads=8)replaces it. The debug-reloader double-scheduler guard comment is updated to reflect that the reloader only applies viaflask run --debug, not the waitress entrypoint.
[0.26.1] — 2026-07-21
Added
- Installer warns before an upgrade that breaks licensing: upgrading from any version older than 0.26.0 (pre-Ed25519 licensing) now shows a page, right after Welcome, explaining that the existing license key will stop validating and the app may show trial-expired with no grace period. The user can Cancel there with nothing installed or changed yet, or Next to proceed. Skipped entirely for fresh installs and for upgrades already on 0.26.0+.
- License status surfaced in the UI: a persistent navbar badge (Licensed / Trial Nd / Trial Expired, linking to
/license) is now shown on every page — previously the trial banner inbase.htmlonly appeared fortrial/trial_expiredstates, so once a license was activated there was no way to see it was active or when it expired short of guessing the/licenseURL. The/licensepage itself now also shows the actual expiry date, not just "days left".
[0.26.0] — 2026-07-20
Fixed
- False orphan-position alerts for arm-bypassed trades: reconciliation compared a strategy's configured mode (
strategy_manager.get_mode()) against the broker, not what a specific trade actually ran as. When the arm-on-restart gate silently downgraded a Real-configured strategy to paper for the day, its state file looked identical to a real trade, so reconcile() kept expecting a broker position that was never placed — flagging a falseorphan_stateerror every ~5 minutes (confirmed live 2026-07-20,DIB_07Jul2026_Kite). Every state-file writer (strategies/engine.py,Lib_v1_1_lot.py,lib_v1_trade.py,niftyStrgl_AI2.py,nifty_strangle_live.py) now stamps the trade's actualexec_mode; reconciliation trusts that per-trade field over the stale config mode when present. - Trade Status modal showing closed short legs as live: a
SHORT_CLOSEDdiagonal rendered its original full leg set (SC/SP/LC/LP) with live LTP-based P&L, even though SC/SP are already closed — misleading whenever their cost basis is stale (e.g. paper-entered then real-exited by the arm-bypass bug above). The modal now renders only the still-open_long_activelegs once a trade isSHORT_CLOSED.
Changed
- Arm state now persists across restarts: previously in-memory only, so every restart silently downgraded all Real-mode strategies to paper until manually re-armed (by design, as a safety gate — see
docs/security.md). Now saved to.armed.jsonand reloaded on startup, at the user's explicit request; an armed strategy stays armed through a restart. Unarmed Real strategies still run as paper and now send one notification per strategy per day (previously only a log line), instead of every monitor tick. - License keys now Ed25519-signed (
licensing.py): the forgeable symmetric HMAC scheme (secret shipped inside the exe) is replaced by Ed25519 signature verification against a compiled-in public-key list; the private key never leaves the operator side. Key format staysexpiry|signature, so the License page and activation flow are unchanged. No legacy fallback — existing customers are re-issued keys carrying their current expiry (personal outreach; old keys stop validating on this version).generate_key()/HMAC_SECRETremoved; signing moves totools/generate_license.py. Grace period extended 3 → 7 days. - Daily online revocation check: once per day the app asks
license.thetaprime.in/v1/license/status(Cloudflare Worker + D1, newlicense-server/directory) whether its key was revoked. Fail-open — an unreachable service never disables the app — and a revocation takes effect only at the next app start, never mid-session. Newstorage.get/set_revocation_state();cryptographypinned explicitly in requirements.
[0.25.3] — 2026-07-19
Changed
- Single source of truth for NSE holidays: removed five duplicated hardcoded holiday sets (
app.py_MARKET_HOLIDAYS,strategies/engine.pyHOLIDAYS_2026,strategies/strategy_template.pyHOLIDAYS, thestrategy_generator.pyAI recipe, andMARKET_HOLIDAYS_2026inLib_v1_1_lot.py/nifty_strangle_live.py). All trading-day gates now resolve through the canonicalholidays.pycalendar (NSE fetch → disk cache → bundled fallback), so a yearly calendar update happens in exactly one place and 2027+ dates stop silently falling through to "not a holiday".SpecEngine(holidays=...)still accepts an injected set for tests; when omitted it now uses the live calendar instead of a frozen 2026 list. Tests pinNSE_HOLIDAYS_JSONto the bundled list via an autouse fixture so date-sensitive gates (e.g. the reconcile-loop holiday test) don't depend on the dev machine's fetched cache.
[0.25.2] — 2026-07-19
Fixed
- Installer overwriting user config on upgrade:
setup.issshippedconfig.json(feature flags, safety limits, broker toggles) as a plain file in the bulk copy, so re-running the installer to upgrade silently reset it to build defaults. The installer now detects fresh install vs. upgrade (Inno Setup's standard uninstall-registry check) and only seedsconfig.jsonon a fresh install; upgrades keep the existing file. Also skips the EULA page on repeat installs and shows a distinct finish message confirming data/credentials were preserved.
[0.25.1] — 2026-07-19
Fixed
- Dhan "session expired" spam from a misclassified Data API entitlement gap: confirmed live 2026-07-19 that a Dhan account without Data API access gets error
810 "ClientId is invalid"on marketfeed/charts calls even though the same credentials work fine for Trading API calls (margins/positions/orders) — the adapter was treating this as a dead session (triggering repeated re-login-required alerts) instead of the same permanent entitlement gap already handled for Dhan's other error shape (451/"not subscribed"). Also fixed the underlying cause of the volume of alerts:notifier.send_notification()now de-dups an identical message within a 10-minute window, since a broker adapter with a stuck-bad token was re-sending the same alert on every poll (each request builds a fresh adapter instance, so its own "alert once" guard never carried across requests).
[0.25.0] — 2026-07-19
Added
- Dhan broker integration: a third supported broker alongside Kite and Breeze — OAuth-style API Key login (like Kite/Breeze) with a manual access-token paste as a fallback, contract-validated before a token is stored, and wired into the JSON-spec strategy/sim engine and multi-day sim. See docs/brokers/dhan.md.
- Per-broker docs:
docs/brokers/now has a dedicated page per broker (kite.md,breeze.md,dhan.md), split out of the old single setup guide. - Real broker icons on the Auth page cards (Kite, Breeze, Dhan), replacing the previous generic styling.
- Per-broker enable/disable toggle: each Auth card has a switch that dims the card and hides that broker's navbar badge live, without a restart — the navbar shows "No broker enabled" if all are off.
Fixed
- Dhan contract check falsely marking the session dead:
contract_validator.pycalled Dhan's error-swallowing adapter methods (margins()/positions()/orders()) instead of the raw client — one failed check's side effect was poisoning the other checks with a misleading "session expired" error instead of the real failure reason.
[0.24.0] — 2026-07-17
Added
- Long straddle / long strangle structures in the Strategy Composer (
long_straddle,long_strangle): buy an ATM call+put or OTM call+put on the same expiry — the long-volatility counterparts to the existing short strangle/straddle. short_entrynotifications:notifier.pynow recognizesshort_entry(diagonal short-leg re-entry) and the composer engine's<LEG>_symbol/<LEG>_entryevent shape — previously this event type produced no notification at all despite live re-entries firing it.- Playwright UI test suite (
tests/ui/): browser-driven coverage for the Composer, login, Reports, indicators, strategy toggle, and AI handoff flows, run vianpm run test:ui.
Changed
strangle/straddlestructures renamed toshort_strangle/short_straddlefor symmetry with the new long variants. Old type keys still resolve everywhere a spec is built or validated via a new legacy-alias registry instrategies/components/base.py, so previously saved strategies keep working unchanged; only the Composer palette shows the new names.
[0.23.3] — 2026-07-17
Fixed
- Misleading "breeze-connect package is not installed" error:
breeze_connectdoes a blocking network call at import time (downloading ICICI's security master); a transient DNS blip there was permanently marking Breeze unavailable for the process lifetime and surfacing a "not installed" message even though the package was present — confirmed in production 2026-07-15. The import now retries on transient network errors (same backoff already used for runtime Breeze API calls), and if it still fails, the token-test and adapter-init error messages show the real underlying reason instead of the hardcoded "not installed" string. - Breeze login silently dropped, bounced to ThetaPrime's own login screen: ICICI POSTs the session token back to
/breeze_callbackcross-site; the defaultSameSite=Laxsession cookie isn't sent on a cross-site POST navigation (unlike Kite's GET-based callback), so the dashboard's login gate saw no session, redirected to/login, and the incoming token was lost before the callback route ever ran — confirmed in production 2026-07-15, reproduces every time since it's a cookie policy, not a transient failure./breeze_callbackis now exempted from the login gate, same as/login//static//license; the token is still validated against ICICI's API inside the route itself. - False "position missing from broker" reconciliation alerts:
KiteAdapter.positions()andBreezeAdapter.positions()caught every exception — including a transient/intermittent broker API error — and returned an empty list instead of raising. Reconciliation read that as "the broker genuinely has nothing open" and flagged every active leg as orphaned, even though the position was still open at the broker — confirmed in production 2026-07-16. Both adapters now let a fetch failure propagate, so reconciliation reports it as a check failure instead of a false mismatch.
[0.23.2] — 2026-07-14
Fixed
- OTM % selector picking ITM strikes for put legs:
otm_pctcomputed the strike the same way for both option types, so a positive percent moved the strike above spot regardless of CE/PE — correct for a call, but in-the-money for a put. It's now option-type aware: positive percent always moves the strike out-of-the-money (above for a call, below for a put); negative moves it ITM. Existing saved strategies (including the live weekly strangle) and Composer templates were migrated to the new sign convention with no change in the strikes they actually pick.
[0.23.1] — 2026-07-13
Added
- Navbar Nifty 50 ticker: primary source is whichever broker (Kite or Breeze) currently has a fresh session; falls back to NSE's public indices endpoint when neither is authenticated.
Fixed
- Entry/re-entry state loss on placement failure: leg-placement and tag-reconciliation logic (previously duplicated three times) is now unified in
_place_legs._run_short_reentrywas missing thePARTIAL_ENTRY_FAILEDstate save thatrun_entryalready had — confirmed live 2026-07-13 — so a real, filled leg could be silently dropped from local records on an unreconciled placement failure. Short re-entry also now verifies fills the same wayrun_entrydoes. - Unreachable long-leg DTE exit: the
long_eodexit check only ran once a diagonal's short legs were fully closed, so it never fired while short legs stayed active (e.g. kept getting re-entered) — confirmed live 2026-07-13. It now runs independently every tick. - Stale "(exit)" price on reopened legs: a leg reopened via short re-entry or rebuild kept its previous exit-price flag, so the dashboard showed the old closing price/P&L instead of the new position's live price.
- Dashboard price priority: live LTP now takes precedence over the one-time long-leg snapshot price, which was never refreshed while a long leg was held.
- Mode filter (All / Paper / Real) selection now persists across Dashboard, Performance, and Reports via
localStorageinstead of resetting on navigation.
Changed
- CI now gates
strategies/engine.pycoverage at 80% — it's the live trading state machine, and each of the bugs above lived in a thinly-covered function there.
[0.23.0] — 2026-07-13
Added
- Three new Exit blocks in the Strategy Composer: Delta stop (
delta_stop) exits when the position's net delta — a Black-Scholes estimate using India VIX as the IV proxy — exceeds ±N, reacting faster to a directional move than a premium-based stop; Volatility stop (iv_stop) exits if India VIX has expanded N% from its level at trade entry (newentry_vixcaptured on every entry/short-re-entry); Profit target (% of premium) (premium_pct_target) exits at N% of the credit collected at entry instead of a fixed points target, adapting across volatility regimes. - Adjustment (roll a leg) block in the Strategy Composer (
roll_untested): when one short leg of a strangle/straddle has decayed to a fraction of its entry premium while the other is under pressure, closes the decayed leg and re-sells it at a fresh strike via its own selector, recentring the position. Guarded by an inversion check (won't cross the other leg's strike), a per-trade roll cap, and a cutoff time; scoped to plain two-short-leg positions only. Ships with a worked example (nifty_strangle_adjustment_12jul2026, disabled by default) and a matching composer template. - Adjustment
sideparam (untesteddefault /tested/both): the same block can now roll the pressured leg instead (further from spot, once it reachestrigger_mult× its entry price) or check both legs each tick and roll whichever qualify — reuses the existing roll plumbing, no new block.
[0.22.0] — 2026-07-11
Added
- Broker API contract check on login: Kite and Breeze tokens are now validated against the read-only endpoints the app depends on (margins/positions/orders for Kite; funds/portfolio/order list for Breeze) immediately after exchange. A failing check means the token is not saved, and the Auth page names which feature would have broken — catching a broken/changed broker API before the market opens instead of mid-strategy.
- Carry position (DTE exit) block in the Strategy Composer (
carry_eod): overrides the previously-unconditional daily EOD close, letting the whole open position (not just short legs — e.g. an iron condor's wings ride with the shorts they hedge) carry overnight until a configurable DTE trigger (0 = expiry day) is reached. Not leg-scopable by design.
[0.21.0] — 2026-07-11
Added
- Ask AI (Phase 0, browser handoff) on Reports, Performance, Multi-Sim results, and the Strategy Composer: builds a context-rich prompt server-side (
ai_prompts.py) and hands it to the user's own ChatGPT, Claude, Perplexity, Gemini, or Copilot session via URL or clipboard. No API key stored, no provider calls from the app.
Fixed
- The background orphan-position reconciliation now only runs on trading days between 09:15 and 16:00 IST. Outside market hours the broker's position book can be stale or incomplete — a Saturday Kite session omitted two genuinely open NRML long legs, producing a false orphan alert every 5 minutes all day. Positions can't change while the market is closed, so nothing is lost; the window extends past the 15:30 close to still catch broker EOD square-offs. Manual
/api/reconcileis unaffected.
[0.20.0] — 2026-07-10
Added
- Iron butterfly, single-leg, and vertical spread structures in the Strategy Composer:
iron_butterfly(ATM-shorted iron condor variant), single-leg blocks (short/long call, short/long put), and credit/debit vertical spreads. - Template loader in the Strategy Composer: a dropdown to load pre-built recipes (matching
docs/composer-strategy-examples.md) into the working draft. Selecting one only fills the browser draft — nothing is saved or schedulable until reviewed and explicitly saved.
[0.19.1] — 2026-07-10
Fixed
reconciliation.pylooked up a strategy's real/paper mode with a hardcoded.pyextension, so spec-based (Composer) strategies always resolved topaperand had their parked long legs (held afterSHORT_CLOSED) dropped from the comparison — the broker's genuinely open positions then showed up as falseorphan_brokeralerts.- Daily loss limit check now normalizes the configured threshold's sign when read, instead of trusting whatever sign is stored in
config.json. A stale/hand-edited positive value inverted the comparison so that any profit under the threshold counted as a breach — today a +26.1pt winning trade falsely disabled all real strategies for the rest of the day.
[0.19.0] — 2026-07-10
Added
- Iron condor structure in the Strategy Composer: short strangle (SC/SP via any selector) with bought wings auto-placed at short strike ± a configurable width (
wing_pts). ATM selectors on both shorts make it an iron butterfly. Wings carry a Black-Scholes fallback price and are placed before the shorts for spread margin. - Composer Strategy Examples doc (
docs/composer-strategy-examples.md, in the in-app docs sidebar): validated ready-to-build recipes for the 9:20 straddle, weekly premium-target strangle, VIX-regime straddle, iron condor, iron butterfly, and diagonal iron butterfly, plus a roadmap of shapes not yet expressible.
Changed
- Strategy Composer guide's block catalog refreshed to cover all registered blocks (VIX floor, combined VIX+gap filter, premium target selector, time-locked/soft/per-leg-price/long-leg exits, re-entry).
Fixed
- Composer UI: structure blocks with their own parameters (e.g. iron condor's wing width) now get a params object on add, so the Block settings panel can edit them.
[0.18.0] — 2026-07-09
Added
- Close paths (manual square-off, closing long legs, expiry exit, re-entry rollback) now confirm each leg's fill and roll back / flag anything unconfirmed, reusing the same tag-reconciliation and LIMIT-conversion machinery entries use — previously these were fire-and-forget, with exit price taken from the last LTP snapshot rather than the real fill.
place_order()on both broker adapters accepts an order tag so a leg can be found at the broker afterplace()raises but the order actually went through (a network drop between broker acceptance and our response).- Kite and Breeze adapters can both rescue a MARKET order stuck OPEN on a thin book by cancelling it and re-placing as a marketable LIMIT priced off live depth.
- Dashboard shows live entry progress (order placed / fill poll attempt / rollback) since a real entry can now take up to a minute of fill polling.
DESIGN.md/PRODUCT.md: design-system reference for the/impeccableskill.
Changed
- Breeze rate-limit responses now retry with backoff like other transient errors; poll interval raised to 3s per leg so concurrent multi-leg fill polling stays under ICICI's account-wide rate cap.
- Loss-limit config values (points / rupees) are normalized to a signed magnitude, so typing
75and-75behave identically.
Fixed
reconciliation.pynow also checks aSHORT_CLOSEDtrade's held long legs against the broker — previously onlyACTIVEstate's top-level legs were reconciled, missing the case where the broker's own EOD MIS square-off silently closed a held long leg with nothing catching it.
[0.17.0] — 2026-07-08
Added
- Breeze adapter resilience against ICICI server-side failures (2026-07-07 saw 114 non-JSON 5xx responses and a mid-day session expiry): read-only calls retry transient errors with short backoff; session expiry is detected, alerted once (App Event Log + notification), and fails fast until re-login;
ltp()serves a last-known price (≤90s old) through blips; ambiguousplace_orderfailures verify against the order book before a single retry, preventing both duplicate and silently missing legs; sustained outages raise a "Breeze degraded" event after 5 consecutive failures. - Trade preview now shows each leg's delta alongside its price.
- Order placement now waits for full confirmation before proceeding: every leg's fill is polled to a terminal broker status; if a leg doesn't confirm filled, the entry rolls back — closing any legs that did fill and cancelling any still-open orders — instead of leaving a partial, unhedged position with no record of it.
- Diagonal entries place both long legs (LC, LP) before either short leg, and confirm both are real filled positions before ever placing a short — a broker's margin engine needs an actual filled long, not just a pending order, to grant spread margin on the paired short.
- Dashboard shows a clear "ENTRY REJECTED — rolled back" status with per-leg outcome (closed / cancelled / still-rejected) for a
PARTIAL_ENTRY_FAILEDtrade, instead of an ambiguous unlabeled state. trade_resetnow refuses to clear live state while a position is active, or when a partial-entry rollback itself had errors — forces manual verification against the broker first.- Scratch parameter-override panel in the Trade Status modal: tweak a leg's
min_delta/ time-value caps for a single Preview run without saving to the strategy, for finding delta-target parameters that actually have a live quote right now.
Changed
- Kite
place_order()setsmarket_protectionon MARKET orders (Zerodha rejects MARKET orders via the API without it) and retries as a marketable LIMIT order — priced off live order-book depth, not the (often stale) last-traded price — when Kite rejects a MARKET order outright for having no recent trade history.
Fixed
delta_targetselector could silently drop itsmin_deltarequirement whenfallback_tv_cap_1collided withmin_time_val, producing a zero-width matching band.- Stale broker quotes priced below an option's intrinsic value (impossible for a live market) were treated as real prices, corrupting strike selection; now rejected as no-quote.
- A second, unvalidated price re-fetch in entry/preview could silently overwrite an already-validated selection price with a stale quote for the same strike, mislabeling it "live."
[0.16.1] — 2026-07-07
Fixed
- Kite margin tag showed ₹0 available despite real account funds —
available.cashreads 0 when funds sit as collateral rather than raw cash; now usesnet, the same figure Kite's own app displays as "Available Margin".
[0.16.0] — 2026-07-07
Added
- Manual "Preview Entry" / "Place Entry Now" controls in the Trade Status modal — resolves what a real entry would do (legs, strikes, and whether each price is a live broker quote or a fallback) without placing anything, so a real entry can be gated on live quotes actually being available.
Changed
BreezeAdapter.ltp()batches option-chain lookups viaget_option_chain_quotes()(one call per expiry/right) instead of oneget_quotes()call per strike, keeping large delta-target scans under ICICI's documented 100 calls/minute limit.- India VIX is now sourced from NSE's public API for Breeze strategies, since Breeze cannot quote it directly.
- A real-entry placement failure now saves whatever legs did fill into state as
PARTIAL_ENTRY_FAILEDand blocks further auto-entry until manually reconciled, instead of losing track of a partially-filled real position. - Crash notifications now quote the last lines of stderr (the actual exception) instead of the first (often unrelated warning spam).
Fixed
- Stale scheduled job ids weren't migrated when a strategy's filename-derived base changed, leaving duplicate entry/monitor cron job pairs.
- Breeze's India VIX
product_typewas invalid ("others"), causing quote failures. BreezeAdapter.place_order()crashed instead of raising a clear error when Breeze returned a plain string inSuccess(an error message) rather than the documented order-confirmation dict.- Breeze's
productvalue for NFO options was sent as Kite's"MIS"/"NRML", which ICICI silently rejected as "Quotes not available"; now sends"options", the only value Breeze'splace_orderaccepts for NFO. - The diagonal structure placed its short put (SP) before its hedging long put (LP), so the broker priced SP as a naked short requiring full margin instead of recognizing the spread; every leg's BUY now precedes its same-side SELL.
- The
atmselector had no fallback price of its own (unlikedelta_target/premium_target), so a single flaky broker quote could abort an entire entry; it now carries the same Black-Scholes estimate. - Real-entry leg prices now all resolve before any order is placed, closing a partial-fill window where a late missing quote could abort mid-sequence after earlier legs had already filled for real.
- The Trade Status modal showed an empty legs table for a
PARTIAL_ENTRY_FAILEDtrade instead of the legs that actually filled. - Clicking Preview Entry / Place Entry Now stacked a second Bootstrap modal on top of the Trade Status modal instead of rendering inline; opening either modal now closes the other first.
[0.15.1] — 2026-07-06
Added
BrokerClient.order_fill()(Kite + Breeze) reads back real order status, filled quantity, and average price for a placed order.
Fixed
- Strategies built from
strategy_template.pyassumed a market order fully filled at the pre-trade quote; entries now poll the broker for the actual fill, and exits close only the quantity that actually filled instead of always closing the full requested size — previously a partial fill on entry could get flipped to an unintended opposite position on exit.calc_pnl()now weights each leg by its own filled quantity so mixed fill sizes across legs still net correctly. reconciliation.reconcile()matched broker positions to strategy state by symbol + direction only, so two strategies holding the same strike (or a partial fill) could both silently match a single broker position line even if the combined quantity didn't actually reconcile. It now sums each strategy's leg quantity per symbol before comparing to the broker's netted line.
[0.15.0] — 2026-07-06
Added
- Shared
toast()/confirmDialog()UI feedback module (static/ui-feedback.js), built on Bootstrap's own Toast/Modal components, replacing nativealert()/confirm()which didn't respect the app's dark theme. Adopted across the data management, index, multi-sim results, option history, performance, reports, and strategy builder pages. - Option history lookup now supports Breeze as well as Kite via a
brokerparameter, fetching candles throughBreezeAdapterwhen requested.
Changed
- Extracted
scheduler.strip_strategy_ext()as the single source of truth for deriving a strategy's job-id/state-file base from its filename, used by bothadd_strategy_realtime_jobs()andapp._trade_state_base()so the two can no longer drift apart.
[0.14.17] — 2026-07-06
Added
- Composer UI now shows the dashboard's live lots value for
fixed_lotssizing (readonly, mirrored from the strategy's dashboard config) instead of the spec's saved default, so the two can no longer visibly disagree.
Fixed
- Running a Breeze-backed strategy without configured Breeze API credentials failed deep inside
BreezeAdapterwith a rawKeyError.app.pynow checks upfront and returns a clear 400 error, andBreezeAdapteritself raises aRuntimeErrorwith guidance if constructed without credentials. fixed_lotssizing read its own specparams.lotsinstead ofctx.lots(the dashboard/STRATEGY_LOTSvalue), so changing lots in the dashboard silently had no effect on live sizing for Composer strategies. It now usesctx.lotsas the single source of truth.
[0.14.16] — 2026-07-06
Fixed
- Trade status modal footer kept showing a stale
Exit HARD_EXIT/Short P&Lsnapshot from the moment short legs closed, even while long legs stayed open and kept moving — the backend only writestotal_pnlonce the entire trade closes, so the footer never had a current figure to fall back to. It now shows a singleTotal P&Lcomputed live from the same per-leg P&L already rendered in the legs table.
[0.14.15] — 2026-07-06
Fixed
- A
.spec.json-based strategy (e.g. Composer strategies) stopped being monitored after any server restart while its real-time jobs were registered — confirmed in production whennifty_strangle_composer_02jul2026.spec.jsonentered a trade at 10:13, then sat unmonitored for ~95 minutes after a 10:13 restart. Root cause:scheduler.py's_refresh_realtime_jobs(), run on every scheduler start to re-register jobs, rebuilt each strategy's filename from its job ID viabase + '.py'. That round-trips for plain.pystrategies, but a spec strategy's job ID is<name>.spec.json_entry, so this produced<name>.spec.json.py— a key absent frommetadata.json, causingget_enabled()to default toFalseand every monitor tick to silently skip as "disabled"._refresh_realtime_jobs()now reads the strategy filename directly from the job's storedargs[0]instead of reconstructing it.
[0.14.14] — 2026-07-06
Fixed
- Running a plain
.pystrategy (not a.spec.json) on a compiled install failed with an argparse error likeunrecognized arguments: --run-strategy-subprocess <path>. Root cause:app.py's--run-strategy-subprocesshandler runs legacy strategies viarunpy.run_path(..., run_name='__main__'), but leftsys.argvas['app.exe', '--run-strategy-subprocess', <path>]— the strategy script's ownargparse.parse_args()then choked on those wrapper args.sys.argvis now reset to[<strategy_path>]beforerunpy.run_path, matching the uncompiledpython strategy_pathinvocation.
[0.14.13] — 2026-07-05
Fixed
- Running/simulating a spec-based (Composer) strategy on a compiled install silently failed (recorded as
rc=2, no useful error) — confirmed via production log during a fullday simulation. Root cause:runner.pyspawns the strategy as a subprocess via[sys.executable, '-m', 'strategies.engine', spec_path], but in a compiled buildsys.executableisapp.exeitself, not a realpython.exe— it has no-m/generic-script support, so this silently launched a second copy of the Flask server instead of the strategy engine.app.pynow intercepts a--run-strategy-subprocess <path>flag at the very top of the module, before any of its own heavy top-level side effects (scheduler.start()etc.), and dispatches directly intostrategies.engine.run()(spec strategies) orrunpy.run_path(..., run_name='__main__')(legacy.pystrategies) instead of starting the server.runner.pyuses this new flag only when compiled; dev-mode invocation is unchanged.
[0.14.12] — 2026-07-05
Fixed
- Running/simulating a strategy failed with
FileNotFoundError: C:\...\ThetaPrime\strategies\<name>.spec.jsonon a compiled install —runner.py'srun_strategy()built the strategy file path fromos.path.dirname(__file__)(the install directory in a compiled build) instead ofconfig.STRATEGIES_DIR(DATA_ROOT), even though the same function already importsSTRATEGIES_DIRand uses it correctly elsewhere.__file__-basedbase_diris still used forPYTHONPATH(needed so the strategy subprocess canimport strategies/broker/config, which do live in the install directory) — only the strategy file's own path was wrong.
[0.14.11] — 2026-07-05
Fixed
- Breeze connection still failed after 0.14.10 with the same
module 'config' has no attribute 'SECURITY_MASTER_URL', even withbreeze_connectcorrectly present as a real on-disk package. Root cause: our ownconfig.pyis Nuitka-compiled (unlikestrategies/broker/breeze_connect, it isn't excluded from compilation), sosys.modules.pop('config', None)doesn't help — the nextimport configfalls through tosys.meta_path, where Nuitka's own finder recognizes'config'as one of its compiled modules and satisfies it directly, without ever consultingsys.path, no matter what's inserted there.broker/breeze_adapter.pyno longer pops-and-hopes; it loadsbreeze_connect's ownconfig.pyfrom disk viaimportlib.util.spec_from_file_locationand places it directly insys.modules['config']beforefrom breeze_connect import BreezeConnectruns — Python checks thesys.modulescache before consulting any finder, so breeze_connect's internalimport configresolves correctly without Nuitka's finder ever being asked.
[0.14.10] — 2026-07-05
Fixed
- Build itself failed with
module 'config' has no attribute 'SECURITY_MASTER_URL'— self-inflicted in 0.14.7's ownbuild.ps1: locatingbreeze_connect's directory usedpython -c "import breeze_connect, ...", which actually imports and executes it (its module level runsurlopen(config.SECURITY_MASTER_URL)), and from the repo root cwd, breeze_connect's bareimport configresolved to our ownconfig.pyinstead of its bundled one — the exact shadowing bug this whole fix chain exists to solve. Now usesimportlib.util.find_spec('breeze_connect').submodule_search_locations[0], which locates the package without executing it.
[0.14.9] — 2026-07-05
Changed
- 0.14.8 missed some of pandas's test-only surface:
pandas.conftest(pytest fixtures),pandas._testing(internal test helpers), andpandas.util._testerstill triggered anti-bloat warnings and slowed the build. Confirmed neither our code nor pandas's own runtime paths use these (only pandas's publicpandas.testingAPI re-exports from_testing, which we never call) — excluded from compilation, not justpandas.testsandpandas.util._test_decorators.
[0.14.8] — 2026-07-05
Changed
- Build time regression from 0.14.7's
--include-package=pandas: it forces Nuitka to bundle pandas's entire tree indiscriminately, including its own internal dev test suite (pandas/tests/, pluspandas/util/_test_decorators.py), which pulls inpytest/pydocand noticeably slows compilation for no runtime benefit.build.ps1now excludes both from compilation.
[0.14.7] — 2026-07-05
Fixed
- 0.14.6's fresh install still showed
Option Scanner/Strategy Builderenabled (should be off for release) and had no strategies visible after saving one via Composer. Root cause:DATA_ROOT/config.jsonnever gets created on a compiled install (nothing seeds it there — the repo'sconfig.jsononly lands in the install directory, a different path fromDATA_ROOT), soapp.py's_load_features()was silently falling back to its own hardcoded defaults, which didn't match the shippedconfig.json. Fallback now matches the intended release values (option_scanner/strategy_builderoff). - Composer-saved strategies were being written to the install directory instead of
DATA_ROOT.strategies/spec.pyhad its own duplicateSTRATEGIES_DIR = os.path.dirname(os.path.abspath(__file__))instead of usingconfig.STRATEGIES_DIR— and sincestrategies/is deliberately excluded from Nuitka compilation (--nofollow-import-to=strategies, so it stays plain, dynamically-editable Python),__file__resolved to the install directory, notDATA_ROOT. Now importsSTRATEGIES_DIRfromconfiglike everything else does. - Breeze connection test failed with "package not installed" — the real error, logged but swallowed into a generic warning:
module 'config' has no attribute 'SECURITY_MASTER_URL'.breeze_connect's SDK does a bareimport configexpecting its own bundledconfig.pyto shadow ours (broker/breeze_adapter.pyworks around this via a temporarysys.path/sys.modulesswap) — butbreeze_connectwas--include-package'd, so Nuitka compiled it into the binary, leaving no real on-disk directory for the swap to point at.build.ps1now excludes it from compilation too (--nofollow-import-to=breeze_connect) and copies the real package directory into the distribution, same treatment asstrategies/broker;pandasandsocketio(breeze_connect's own runtime deps, previously only bundled as a side effect of statically following into breeze_connect) are now included explicitly so excluding it from that static-follow analysis doesn't silently drop them.
[0.14.6] — 2026-07-05
Fixed
- Fresh installs on a clean machine still landed
DATA_ROOTsomewhere invisible to the user — confirmed via the diagnostic log line:DATA_ROOT=C:\WINDOWS\system32\config\systemprofile\AppData\Roaming\ThetaPrime. The__compiled__detection itself was working correctly; the problem was thatnssmruns theThetaPrimeDashboardservice asLocalSystemby default, and%APPDATA%for that account resolves to the hiddensystemprofileprofile, not any real user's.config.py's_data_root()now uses%ProgramData%for the compiled/frozen path — the standard Windows location for a service's own data, independent of which account runs it.
[0.14.5] — 2026-07-05
Fixed
/api/today-pnl(and any other lazyfrom config import ...) could intermittently 500 withImportError: cannot import name 'DATA_ROOT' from 'config', resolving tobreeze_connect's bundledconfig.pyinstead of ours. Root cause:broker/breeze_adapter.pyworks aroundbreeze_connect's SDK doing a bareimport configinternally by temporarily poppingsys.modules['config']and prependingbreeze_connect's own directory tosys.path— but (a)breeze_connect's own import machinery also inserts its directory intosys.pathindependently, so removing only our own insert left a duplicate behind permanently, and (b) if this workaround ran before our ownconfig.pyhad been imported anywhere in the process, there was nothing to restore, so the pop leftconfigout ofsys.modulesentirely with the pollutedsys.pathstill in place — meaning any laterimport configanywhere would resolve tobreeze_connect's copy instead of ours.broker/breeze_adapter.pynow snapshots and restores the fullsys.pathlist rather than trying to track a single insert/remove.app.pyalso now eagerly importsbroker.breeze_adapterright afterconfig(before the startup-reconciliation background thread or any request thread can start), closing the narrow window where a concurrentfrom config import ...could still race the temporary pop.
[0.14.4] — 2026-07-05
Fixed
- 0.14.3's
sys.executable-based fix for the installer's data path still wrote toProgram Filesinstead of%APPDATA%\ThetaPrime: the new startup diagnostic log line showedDATA_ROOTresolving to the repo-relative branch even thoughsys.executableprinted asapp.exelater in the same run — meaningsys.executable's value atconfig.py's import time didn't match what it read later, so it isn't a dependable signal after all._data_root()now checks Nuitka's__compiled__marker onsys.modules['__main__'](the compiled entry point,app.py) instead — a documented Nuitka guarantee that doesn't depend on import-order timing, unlike checking__compiled__onconfig.py's own module globals (which 0.14.2 tried and which doesn't propagate reliably to non-entry modules) orsys.executable(which 0.14.3 tried). - The startup diagnostic log line now also reports
main.__compiled__so any future data-root regression is diagnosable from the log alone.
[0.14.3] — 2026-07-05
Fixed
- Packaged installer ran in dev mode (exposing dev-only nav items, endpoints, and disabling login on localhost) because a standalone install has no
.gitdirectory:git rev-parsefailed silently (non-zero exit, no exception) and the empty branch name fell through to'dev'instead of'prod'._detect_env()now treats a failed git lookup as'prod'. - Composer's pre-seeded "ATM Strangle" example strategy failed to save with
unknown parameter for 'atm'. The default leg selector (alphabetically first in the catalog,'atm') was never updated to'otm_pct'when the seed template set itspctparams, so validation rejected it — this blocked creating/enabling any strategy on a fresh install. - 0.14.2's
__compiled__-based fix for the installer's data path (writing toProgram Filesinstead of%APPDATA%\ThetaPrime) didn't actually work —_data_root()now keys offsys.executableinstead, which Nuitka standalone reliably replaces with the compiled binary itself (a documented, deliberate behavior, unlike__compiled__'s per-module injection).
[0.14.2] — 2026-07-05
Fixed
- Packaged installer wrote all app data (config, secrets, strategies, databases) next to
app.exeinProgram Filesinstead of%APPDATA%\ThetaPrime, because Nuitka standalone builds never setsys.frozen(a PyInstaller/cx_Freeze-only convention) —_data_root()now also checks for Nuitka's__compiled__marker.
[0.14.1] — 2026-07-05
Changed
- The reconcile badge now shows the affected symbols (broker-only vs state-only) directly beneath it on a mismatch, instead of only in a hover tooltip.
Fixed
- Reconcile falsely flagged every active paper-mode strategy as an orphan position, since it compared paper trades (which never touch the broker) against broker positions like real ones. Paper-mode legs are now excluded from the comparison entirely.
[0.14.0] — 2026-07-05
Added
- Orphan-position reconciliation now runs periodically in the background (every 5 min, not just at startup), gated by a new Safety Settings toggle ("Automatic orphan-position reconciliation", on by default).
Changed
- Replaced the dashboard's orphan-position red banner + Reconcile button with a small status badge next to Strategies (gray/green/red), which also auto-refreshes every minute.
Fixed
- Breeze connection test (
/test_breeze_session) failed withAttributeError: ... has no attribute 'SECURITY_MASTER_URL'because it importedbreeze_connectdirectly instead of going through the existing guarded import — now routes through the same adapter path used elsewhere. - Reconcile failed with
EnvironmentError: API_KEY env var is not setwhen run from the web UI (as opposed to a strategy subprocess) becauseget_broker()never injected stored credentials into the process environment. It now does, for both Kite and Breeze. - Reconcile falsely flagged every open Breeze option position as a mismatch:
BreezeAdapter.positions()returned the bare underlying (stock_code) instead of the composed Kite-style tradingsymbol reconcile compares against. It now reconstructs the full symbol fromstock_code/expiry_date/strike_price/right.
[0.13.0] — 2026-07-04
Added
- Orphan position detection: compares active state files against the broker's live positions and alerts on any divergence (dashboard banner + notification), without ever auto-closing a position. See
docs/security.md. - Data Management page (
/data): download/restore a full backup of app data (auto-snapshots before restore), plus one-click simulation and temp-file cleanup. Seedocs/data-management.md. (#82)
Changed
Fixed
performance.db,run_history.db,sim_sessions.db, and the app event log now live underDATA_ROOTinstead of the source directory, so they're included in backups and land in%APPDATA%\ThetaPrimein the packaged installer like the rest of the app's data.
[0.12.8] — 2026-07-04
Changed
installer/publish-release.ps1now always sets a dedicated product identity (Deepak Sharma <info@thetaprime.in>) for commits toThetaPrime-Dist, instead of falling back to the personal git identity used for the main source repo. Note: the GitHub Release itself is still attributed to whichever account's token created it (GitHub's account model, not something git config controls).
[0.12.7] — 2026-07-04
Fixed
installer/publish-release.ps1's$ErrorActionPreference = "Stop"doesn't actually halt on a failed native command (git commit,git push, etc.) — discovered when the v0.12.6 publish silently continued past agit commitfailure (no git identity configured in the freshly-clonedThetaPrime-Distcheckout) and reported success despiteversion.jsonnever actually being updated. Added explicit$LASTEXITCODEchecks after every native git call, and the script now sets a local git identity in the clone if none is configured.
[0.12.6] — 2026-07-04
Changed
- Removed
release.ymlandwindows-build.yml: a real v0.12.5 release attempt showed Nuitka's--standaloneC-compile takes 2+ hours (still running when cancelled) on a hosted GitHub Actions Windows runner — it never completed a build there. The installer build andThetaPrime-Distpublish step now always run locally:installer\build.ps1then the newinstaller\publish-release.ps1(tagsmain, creates the GitHub Release + uploads the installer, commits a freshversion.json).installer/build_version_json.pyextracts the manifest-building logic previously inlined in the removed workflow.DIST_REPO_TOKENis no longer used by anything.
[0.12.5] — 2026-07-04
Fixed
release.ymlandwindows-build.ymlnever installed the project's Python dependencies before runninginstaller/build.ps1— the Nuitka compile step (added in 0.12.2) has been failing withNo module named nuitkasince it was introduced, masked until now by the earliernssm.ccfailure in the same script. Both workflows nowpip install -r requirements-prod.txtbefore building.
[0.12.4] — 2026-07-04
Fixed
installer/build.ps1had a hard runtime dependency onnssm.ccbeing up during every CI release build, with no fallback — an outage there (503observed during the v0.12.3 release attempt) hard-fails the whole release. Vendoredinstaller/nssm.exe(NSSM 2.24, verified 64-bit binary) into the repo; the existingTest-Pathcheck already skips the download when the file is present, so CI no longer depends on nssm.cc's uptime at all.
[0.12.3] — 2026-07-04
Fixed
square_off()andclose_long_legs()read/wrote active-trade state from the app's install directory (BASE_DIR) instead ofconfig.STRATEGIES_DIR(%APPDATA%\ThetaPrime\) — invisible in dev (the two paths coincide when not frozen) but would silently lose state on every update in a real Nuitka-frozen install (app.py)- Same bug in
runner.py: the max-lots safety clamp readconfig.jsonfrom the install directory, and the pre-entry re-entry guard read active-trade state from the install directory — both silently no-op via a bareexcept: passin production, meaning the lot cap and duplicate-entry guard could be silently disabled after an update - Removed a dead, duplicate update-available banner in
templates/base.htmlthat polled/api/version/checkclient-side and pointed at a non-functional "your account portal" link; the server-rendered banner (with a working Download link) already covers this
Changed
release.ymlnow publishes the GitHub Release andversion.jsonto the publicdsharma01/ThetaPrime-Distdistribution repo instead of the private source repo, matching whatapp.py's version-check already reads from- Deploy process reconciled:
CLAUDE.md's routine deploy checklist now updatesCHANGELOG.mdon every version bump; tagging/installer publish is documented as a separate, occasional "commercial release" step, not part of every deploy.docs/dev/release-strategy.mdandwindows-build.ymlupdated to match — therelease/*branch ceremony was dropped since it wasn't actually being followed - Backfilled
CHANGELOG.mdentries for 0.11.0–0.12.2, which had been skipped since the deploy process forked from the documented release flow
[0.12.2] — 2026-07-04
Changed
- Installer build (
installer/build.ps1) now compiles core app modules with Nuitka (--standalone) instead of shipping plaintext.py+ a bundled venv, so the distributed installer no longer ships readable source for the core app.strategies/andbroker/are excluded from compilation and still ship as plain Python (dynamically editable).nssm_setup.batandrequirements-prod.txtupdated to match.
[0.12.1] — 2026-07-03
Fixed
- Widened
delta_targetfallback bands and fixed the spec engine's day-done lookup (strategies/engine.py,strategies/components/selectors.py) - Strategy composer UI now displays a spec strategy's configured params instead of leaving them blank (
templates/index.html)
[0.12.0] — 2026-07-03
Added
- Re-entry component and day-candle caching for the strategy composer framework (
strategies/components/reentry.py,broker/day_candle_cache.py), plus expanded exit and selector blocks (strategies/components/exits.py,selectors.py) - Deploy verification steps and a branch/merge safety protocol documented in
CLAUDE.md
Fixed
- Restored the animated splash login page, lost during the trial-licensing merge (
templates/login.html)
[0.11.0] — 2026-07-02
Added
- Trial-based licensing: 15-day simulation-only trial, no activation required on first install (
licensing.py,templates/license.html) - Update management:
config.pyadds aDATA_ROOTconstant separating user data (%APPDATA%\ThetaPrime\) from the app install directory; a background thread checks a hosted version manifest and shows an in-app update banner - Strategy engine: short re-entry support, VIX + gap filter, broker-agnostic trade LTP fetching (
broker/sim_ltp.py) - Windows installer: NSSM service integration fixes (quoted
AppParameters,.pth-basedsys.pathsetup for the embeddable Python build), auto-cleanup of the staging folder after build, bundled installation guide PDF - Docs restructured — superseded strategy docs moved to
docs/archive/, newdocs/dev/installation.md - CSV export for dashboard tables (
static/table-csv-export.js)
Fixed
- Update manifest's
download_urlvalidated ashttps://before being injected into the banner link - Version comparison now uses tuple comparison instead of lexicographic string comparison, avoiding an ordering bug (e.g.
"0.9.0" > "0.10.0"as plain strings)
[0.10.2] — 2026-06-30
Fixed
- Trade Status modal showed the wrong strike for composer/spec-engine trades (e.g.
723900instead of23900) and never showed Delta/IV for long legs — leg selectors now returnstrike/delta/iv_pctand the engine persists them instead of relying on the frontend to regex-parse the strike out of the symbol - Same strike-display bug in
nifty_strangle_live.py— its dashboard leg dict now includesstrikefrom the live position - Manual square-off computed zero P&L for strategies (
nifty_strangle_live.py) that store leg symbols with theNFO:exchange prefix already included —square_off()no longer double-prefixes the symbol when fetching LTP nifty_strangle_live.pycould go unmonitored for the rest of the day: a monitor tick atMIN_ENTRY_TIMEthat ran before that day's entry would mark the day complete and never get cleared, silently skipping every subsequent monitor tick even with a live position open- Scheduler hardcoded every strategy's entry job to fire at 09:45 IST regardless of its own
entry_timeparam, sonifty_strangle_live(entry_time 10:00) always self-rejected its scheduled entry —add_strategy_realtime_jobs()now reads each strategy's configuredentry_time - Strategy list no longer lists
payoff.pyas a runnable strategy
0.5.0 — 2026-06-21
Added
- Multi-day simulation runner (
multi_sim.py) — iterates over a date range, runs a full-day sim per trading day, and stores results insim_sessions.db sim_sessions.py— SQLite store for multi-day simulation sessions and per-day P&L resultstemplates/multi_sim_results.html— results page showing per-day breakdown and aggregate statsholidays.py— NSE holiday calendar fetched from NSE public API, cached in.nse_holidays.json, with bundled fallbackconfig.json— feature flags file (option_history,option_scanner) to enable/disable sections at runtime- Strategy parameter JSON files (
lib_v1_trade.json,niftyStrgl_AI2.json,nifty_strangle_live.json,strategy_template.json) as companion metadata for each strategy runner.pymulti-sim API endpoints to start/cancel/poll multi-day simulation sessionsstrategy_manager.pyimprovements for parameter schema management
Changed
sim_engine.pyskips NSE holidays automatically viaholidays.is_trading_day()broker/breeze_adapter.pyandbroker/sim_ltp.pyreliability improvements- Strategy files (
lib_v1_trade.py,niftyStrgl_AI2.py,nifty_strangle_live.py,strategy_template.py) updated to use parameter JSON schemas templates/index.htmlandtemplates/reports.htmloverhauled with expanded controls and improved layoutstatic/style.cssextended with multi-sim and reports page styles
Removed
strategies/niftystrangle_aiGen.py— superseded byniftyStrgl_AI2.py
0.4.2 — 2026-06-20
Added
- Broker dropdown in the Simulate modal — pre-selects the strategy's configured broker (Kite/Breeze) but allows per-run override without touching metadata.json
runner.run_strategy()acceptsbroker_overrideparam that takes precedence over the per-strategy metadata settingbroker/__init__.get_broker()logsBroker selected: kite/breezeat startup so logs are unambiguousKiteAdapter.__init__logsKiteAdapter: session ready(mirrors existing BreezeAdapter session log)
0.4.0 — 2026-06-20
Added
- Dark/Light theme toggle with localStorage persistence
templates/base.html— Jinja2 base template with shared navbar and theme toggle buttonstatic/theme.js— Theme switcher logic (☀�/🌙 icon, instant theme application)- Light mode CSS tokens and navbar overrides in
static/style.css - All templates refactored to extend
base.htmlfor consistent UI - Option Scanner page with premium % filtering
scanner.pymodule: scan NFO options by premium as % of spot price- Two scan modes: specific underlying or top N F&O stocks (by value/volume)
- IV (implied volatility) and delta calculation for each option
- Sortable results table with CE/PE row highlighting
/api/option_scan/*endpoints for expiry listing, scanning, and top stocks- Inline runner status per-strategy on the main dashboard
- Full-day simulation engine fixes and improved reliability
- WhatsApp notification toggle (
set_notifications_enabled/is_notifications_enabledin storage) - Notification toggle UI on Auth page with live HTTP status feedback
sim_engine.pysmoke tests
Changed
Fixed
0.3.0 — 2026-06-18
Added
- Multi-broker support:
broker/adapter package withBrokerClientABC andBrokerConstantsdataclass KiteAdapterwraps the existing KiteConnect flow; strategies continue to work unchangedBreezeAdapterwraps ICICI Breeze API with automatic NFO symbol parsing (weekly + monthly expiry)- Per-strategy broker selection via
"broker"field instrategies/metadata.json runner.pyinjectsBROKER,BREEZE_API_KEY,BREEZE_API_SECRET,BREEZE_SESSION_TOKENinto strategy subprocesses- Breeze credential storage in keyring (
set_breeze_credentials,get_breeze_credentials,set_breeze_session_token) - Auth page Breeze card: API key/secret form, session token form, and connection test button
- Per-strategy broker selector dropdown on the main dashboard
PORTenv var support inapp.pyfor flexible deploymentpytest.iniwithpythonpath = .sopytestresolves project-root modules in CIPYTHON_KEYRING_BACKEND=keyring.backends.null.Keyringin CI workflow to preventNoKeyringErroron Linux
Changed
lib_v1_trade.py,nifty_strangle_live.py, andstrategy_template.pynow useget_broker()/BCconstants instead of importingKiteConnectdirectly
0.2.0 — 2026-06-14
Added
version.pyas single source of truth for application version- Tag-triggered GitHub Release workflow with versioned Windows exe
- Manual Windows build workflow triggered on
release/**branches - Branch protection on
mainanddevelop CHANGELOG.mdand PR templatedocs/release-strategy.mddocumenting branching and release processdevelopbranch for integration,feature/breeze-integrationfor multi-broker work
0.1.0 — 2026-06-14
Initial release tagged as v0.1 on GitHub.
Added
- Flask web dashboard for Kite Connect token management
- Strategy manager with metadata-driven CRUD
- APScheduler integration with SQLite jobstore
- Paper trading and live simulation modes
- Full-day simulation using 1-minute Kite historical candles
- Performance and reports pages with Chart.js
- WhatsApp alerts via Bailey notifier
- Windows exe build via PyInstaller
- CI workflow with pytest