github marketcalls/openalgo openalgo-eventlet-stability-security
v2.0.2.2

4 hours ago

Version 2.0.2.2 Released

Date: 29th August 2026

Stability and Security Release: closes the eventlet boundary defects behind the long-standing "the first order works and the next one hangs the app" reports, sweeps broker credentials out of every log surface across 30 plugins, stops Dhan symbol mapping routing an order to the wrong instrument, and opens the charting terminal to user-written indicators loaded at runtime with no build step. Also modernises the Motilal Oswal plugin, extends GTT to Angel One, Fyers and Upstox, gives Flow's order fields payload-driven values, and cuts a fresh install's clone from 276 MB to 20 MB

This release spans 143 commits since 2.0.2.1 and is dominated by two sweeps that touch the whole tree rather than one surface.

The first is the eventlet boundary. Production runs gunicorn --worker-class eventlet -w 1, which monkey-patches the stdlib before the app is imported, so threading.Lock, Event and Queue all become green primitives owned by the hub. A handful of threads in this project are genuinely real OS threads, principally the asyncio loop in the websocket client, and every crossing between the two worlds was a defect: a real thread touching a green lock raises greenlet.error: Cannot switch to a different thread inside fire_timers and blocks that thread forever, a greenlet waiting on a C-served timeout freezes the entire worker, and a waiter that is simply never woken sits out its whole timeout on data that was ready all along. All three were present. None of them reproduce on uv run app.py, which is why they survived three separate issue reports (#1402, #1473, #1569) over months.

The second is credential exposure in logs. A grep for credential variable names finds the easy half. The larger class is a secret riding inside something else that gets logged: a WebSocket URL with the token in the query string, an auth request or response body, a headers dict, an exception message whose text embeds the full URL. Roughly twenty broker adapters were also actively replacing a redacting logger with a bare logging.getLogger(). Every candidate here was rendered with a realistic value and run through the production SENSITIVE_PATTERNS rather than read, which is what separated the 2 real leaks from the 5 sites already safe.

Outside those, the charting terminal gained runtime-loadable user indicators and moved from openalgo-charts 1.6.0 to 1.8.2, Flow had its 61 node types audited against the executor, the Dhan and Motilal Oswal plugins were substantially repaired, GTT reached three more brokers, and the repository stopped committing 165 MB of reproducible compression artifacts.


Highlights

  • The eventlet boundary, in four commits (27799885, 9a488cae, 05496d8b, ebf03340, #1402, #1473, #1569) - what users reported was the first order going through and the next one hanging the app for over a minute, with the order itself having taken 62 milliseconds. Four separate defects, each fatal on its own.

    A lock shared with the loop thread must be real. The websocket client runs its asyncio loop on a real OS thread, because asyncio cannot run on a green one, and it invoked every registered market-data, auth and error callback from that thread. Four crossings existed: WebSocketClient.lock, the sandbox engine's _lock, the scalping risk monitor's _lock and the Flow executor's tick data_event. Contended, the hub tries to resume a waiter belonging to another thread and leaves the loop thread blocked on that lock forever, so the feed stops answering pings and stops resolving subscribe acks. They now take their primitives from utils/real_threading, which resolves to the unpatched originals under eventlet and the stdlib otherwise. The Event needed more than a swap: a green Event never wakes on a set() from a real thread, and a real Event.wait() wakes but blocks the hub, so real_threading.wait_for polls the flag and yields between checks.

    Logging handler locks and inline callback dispatch. logging.Handler builds its lock in __init__, which under gunicorn happens after monkey-patching, so it is green, and every real thread in this project logs. Measured, a real thread logging while a greenlet holds the handler lock deadlocks permanently and raises exactly the production traceback; #1569 reports the give-away that precedes it, AttributeError: 'StreamHandler' object has no attribute 'lock' on unrelated requests. Handler.createLock is now patched on the class, so third-party handlers get a real lock too. Separately, _handle_message ran on the loop's real thread and called every subscriber inline, and those subscribers reach SocketIO, the event bus and the sandbox engine. The loop thread now only enqueues onto a real bounded queue and a green thread does the calling.

    PRAGMA busy_timeout waits inside C. A greenlet waiting that way freezes the entire worker for the whole timeout, and that is not merely slow: the greenlet holding the write lock cannot be scheduled to commit while the waiter is blocking the hub, so the wait can only ever end in "database is locked". Measured, a holder that needed the lock for 0.5s produced a 16.46s failure; waiting the same 0.5s in Python succeeds in 0.47s with the hub running. The in-SQLite wait drops to 100ms and the real waiting moves into a Python retry loop, keeping the same 15-second total budget.

    run_coroutine_threadsafe never wakes its caller. It hands back a concurrent.futures.Future whose result() waits on a green threading.Condition while the loop thread resolving it is real, so the future resolves correctly and the caller sleeps on regardless. Measured, an ack that arrived in 0.3s cost the caller the full 10s, which is why subscribe() took its whole 12 seconds whenever it had to wait at all. Nothing crosses the boundary now except one plain boolean.

    test/test_eventlet_cross_thread_locks.py and test/test_sqlite_lock_cooperative.py prove each direction under a real hub, in a subprocess because monkey_patch() cannot be undone, asserting on elapsed time and hub liveness rather than return values, which were always right. 95d6d1fa records the whole rule set in CLAUDE.md so the next person does not rediscover it.

  • Broker credentials no longer reach the logs (209c305e, 438af07f, c8dad476, fe0875f3, 4c3abf7f, #1854, #1855) - four passes, each finding a class the previous one could not see.

    73 call sites across 60 files in 30 plugins were using a bare logging.getLogger(), which bypasses SensitiveDataFilter. This was not merely a missing filter: websocket_proxy/base_adapter.py already sets a redacting logger, so every adapter subclass reassigning self.logger in its own __init__ was actively replacing it with a non-redacting one.

    Credentials interpolated directly into messages, across 12 plugins. Removed entirely rather than truncated, because a 10-character API key prefix is still key material and fivepaisa logged the first 50 and last 50 characters of its access token on adjacent lines, which together expose far more of a JWT than either alone.

    Secrets riding inside a URL, payload, headers dict or exception message: 35 sites across 24 files in 15 plugins. The worst were flattrade logging hash_input, which is literally the API key concatenated with the secret; definedge putting the API key in the URL path and then logging the httpx.HTTPStatusError whose message embeds the full URL, so it leaked on every 4xx, which is exactly the wrong-credential case; aliceblue logging the full login response at INFO on every login; and upstox logging the AWS SigV4 presigned feed URL including X-Amz-Signature.

    A defect in the redaction pattern itself. (Bearer\s+)[\w\-\.]+ stops at the first separator, and two brokers send a two-part credential in that header, so the filter redacted the harmless half and left the real secret. Found independently in tradejini and aliceblue, which is what proved the pattern was wrong rather than the call sites. cookie was added to the alternation, utils/logging.py gained its first tests, and the frontend stopped sending mutating requests with no CSRF token when fetchCSRFToken() throws.

  • Dhan: symbol mapping routed orders to the wrong instrument (da553a56, 49717118, #1929, #1930, #1932, #1934, #1924) - four related defects in the master contract and the feed, all verified against the live 197,254-row scrip master.

    Dhan scopes SEM_SMST_SECURITY_ID per segment, not per exchange, and assign_values matched on instrument name alone. NSE segment D and segment M both mapped to NFO, giving 8,642 security ids two contracts each: token 153964 was both a TCS option and a SILVERM option, and get_symbol() returns the first match. A live equity option position could therefore resolve to a commodity symbol, the position book lookup would miss it, and a strategy would conclude it was flat and skip the exit. Colliding rows go from 17,284 to 0, NSE segment M now maps to NCO with 25,400 rows, and 722 stranded MCX index option rows are recovered.

    On the forward lookup, symbols were built from display strings. The equity branch ignored SEM_SERIES, so ELECTCAST was both the equity and its warrant and an order could reach the wrong one; strikes were parsed from the two-decimal SEM_CUSTOM_SYMBOL, which misformatted 1,628 NFO contracts as .50CE and, in currency, collapsed EURUSD 1.010 and 1.015 onto one symbol. Breaking: 7,190 NSE equity symbols gain a series suffix (ELECTCAST-W1, matching Zerodha's convention). Only 9 were genuinely ambiguous and the rest are mostly bonds and government securities, but the BE, SM and ST series include tradable equities, so a strategy referencing one by its bare symbol needs updating. No migration is required, since master_contract_download() clears the table before reinserting.

    unsubscribe() only forgot the instrument locally and never told the broker, so subscriptions accumulated toward the 5,000-per-connection limit invisibly, Dhan not acknowledging subscribes. dhan_sandbox was worse: it sent {"RequestCode": 0}, which is not a valid code at all. Both now send the code matching the mode the token was subscribed in. dhan_sandbox also carried byte-identical copies of every mapping bug above and is brought to parity.

  • User chart indicators, loaded at runtime (f4359718, a95460fe, #1923) - drop a .js file in strategies/indicators/ and it becomes an indicator in the /trading picker, with a generated settings dialog, legend row and saved-layout persistence. No build step, no Node.js, no restart. They are loaded over HTTP rather than bundled, because frontend/dist/ is built by CI from what is committed and user indicators are deliberately gitignored, so a bundled one would be erased by the next git pull. The folder sits under strategies/ to mirror strategies/scripts/, which also puts it inside the openalgo_strategies Docker volume. Custom indicators register after the built-in tier, so a colliding id overrides a built-in. Validation happens in the browser against the library the chart already has loaded, catching both silent killers: a column that is not bars.length long, and a plot key calc never filled. Neither raises anything at runtime, which is what made a broken indicator look like one that simply does not work.

  • Charting terminal: 1.6.0 to 1.8.2, and everything the pin was hiding (8eda625d, cbacb83b, 0dc8ac19, a1a2dc92) - the pin sat at 1.6.0, so none of the descriptor surface added since was reachable. Bumping it exposed a chain of gates that would each have rejected a valid indicator before the user saw it: the loader accepted six input types where the library now defines eleven, the settings dialog could render none of the five new ones, and the column check assumed one column per plot so a candle plot fed by an ohlc group read as four missing columns. Separately, the chart was never told the instrument tick size, so its price scale inferred precision from the visible range and RELIANCE rendered as 1,303.5 rather than 1303.50 while drawings snapped to a grid nobody chose. The terminal already had the number from the symbol master and simply never passed it. The version is now pinned exactly rather than with a caret: ^1.6.0 resolves to 1.9.0 on the registry, and deprecation only prints a warning, it does not stop an install.

  • Charting terminal features (5ea4d080, d7cdbf54, c0d73cee, 55447bd0, 7b867c9d, 0c8c29a6) - market replay with a transport bar and scrub; a chart settings dialog rendered from the engine's own declarative schema across five tabs, with a reset that resets to this terminal's baseline rather than the engine's per-control defaults; chart sync groups linking crosshair, time and symbol independently; a warm-load history cache, measured at two history requests for a cold symbol and one for a return visit; toolbar undo and redo for drawings; and an indicator browser with categories, favourites and recents replacing a single 91-entry scrolling column. Plus the corner session clock reading the exchange's wall time through the chart's configured timezone.

  • Motilal Oswal: repaired and modernised (f76818be, #1912) - every endpoint moved to its documented version (placeorder v1 to v2, modifyorder v2 to v5, getorderbook v2 to v5, gettradebook v1 to v4, getposition v1 to v4, getdpholding v1 to v3). get_open_position matched a broker scripname against a position-book symbol and never matched, so every smart order saw a flat position; smart orders also looked up positions with no exchange, so F&O MIS positions never matched. The env convention was corrected: sending the secret as ApiKey caused MO2005, and the client code is now persisted from the TOTP page rather than read from an env var, fixing the empty vendorinfo (MO2012). Then a WebSocket audit found four leaks: the services build a fresh BrokerData per request so an instance-cached socket could never hit, and every depth and multiquote call opened a brand new WebSocket that nothing closed. Pooled per session in a module-level registry, five requests now open one socket instead of five. Three further leaks surfaced on re-audit: a cold-start registration race (4 concurrent requests created 4 sockets and leaked 3), duplicate poll threads on every proxy recovery path republishing every subscription to ZMQ, and tick caches never purged, which an option-chain sweep grows by hundreds of strikes per refresh. Pinned by eight tests mirroring test_aliceblue_ws_reuse.py.

  • GTT for Angel One, Fyers and Upstox (6d7f2552, #1922) - extends GTT beyond Zerodha and Dhan. Each broker registers itself by shipping api/gtt_api.py plus mapping/gtt_data.py, with the compatibility matrix derived by parsing for it. Angel One has no OCO rule type, so an OpenAlgo OCO becomes two rules returned as a composite trigger id, with the first leg rolled back if the second fails. Fyers signals OCO by the presence of orderInfo.leg2 and requires leg1 above the LTP, so the target leg maps to leg1. Upstox has no OCO at all: every GTT needs an ENTRY rule, making its multi-leg form a bracket rather than an exit-only pair, so an OpenAlgo OCO opens the entry position before arming the legs, logged loudly at place time because the resulting EDIS rejection names demat authorisation rather than the entry leg.

  • Flow: nodes no longer act on data they do not have (cb954658, ce9db84a, 44ebce13, 732afe4d, 98e693db, 252bc7c1, #1904, #1909) - an audit of all 61 node types, each finding reproduced before it was touched. priceCondition, positionCheck and fundCheck read the broker response without checking its status, so a 401 gave LTP 0.0 and status: success: "if LTP < 100 then BUY" fired on an expired session and "if no position then BUY" doubled an open one. An errored condition settled a gate with its placeholder False, driving a real order down the FALSE branch before the run was marked failed. A condition reachable by two paths ran twice and placed two orders from one trigger. timeWindow could not cross midnight, making 22:00-02:00 unsatisfiable and, inverted, always true. Subscriptions were never given back to the process-wide websocket singleton, and a specific-mode unsubscribe with no symbol fell through to unsubscribe_all, clearing the subscriptions the sandbox engine uses to trigger pending SL and LIMIT orders. Two injection paths in httpRequest: the URL was interpolated twice so a payload could substitute its own {{...}}, and header JSON was interpolated before parsing so a value carrying a quote became structure.

    Separately, every order field now accepts a {{reference}}, not just symbol: a dropdown cannot express {{webhook.exchange}} and a number input cannot express {{webhook.quantity}}, so a webhook-driven order could name its instrument and nothing else. The executor always interpolated these; the gap was entirely in the form. Interval schedules are anchored to the clock rather than to activation, so a 5-minute job lands on :00, :05, :10 rather than five minutes after a button press, offset by FLOW_INTERVAL_ALIGN_OFFSET (2s) so the run sits just inside the new bar. Derivative segments default to NRML rather than a stored MIS on every node. MCX commodity options are resolvable, priced off the nearest unexpired future since there is no spot instrument, and multi-leg baskets can be built leg by leg instead of only through hand-written JSON.

  • Shoonya returns a quote for a different instrument, 9% of the time (252bc7c1, part of #1904) - measured against the live API, two NFO options polled round-robin at 2 req/sec for 13 minutes: 119 of 1318 replies came back as a complete, stat=Ok snapshot of another instrument, every one of them the NSE index, in a run that never requested the index. Nothing about the price reveals it: a leaked index quote is internally consistent and its LTP sits inside its own OHLC, so the stale-quote check cannot catch it. It reaches order fills, and a sandbox MARKET buy on NIFTY18AUG2624600CE filled at 24391.25, the NIFTY spot, against an LTP of 39.85. All five GetQuotes paths now compare the echoed exch/token against the request and retry up to three times; the two multiquotes fetchers bypassed get_api_response entirely and had no check at all. The same session's WebSocket feed carried 1719 ticks with zero wrong instruments, so this is specific to the REST endpoint.

  • Repository size: 276 MB to 20 MB per install (4fe47bd0, 02361622, a557c12e, #1896, #1897, #1898) - the Vite build emitted .br and .gz next to each hashed asset and CI force-committed them on every rebuild. Compressed output can be neither deflated nor delta-compressed by git, and content-hashed filenames mean every rebuild produces new blobs that never go away, so these had grown into 165 MB of the 248 MB frontend/dist occupies in history. Repository history had tripled in 60 days. They are generated at startup now, idempotently and atomically, in about 30 ms on a warm boot. Tracked dist files drop from 586 to 270 and history growth from roughly 83 MB/month to 28 MB/month. Alongside it, all four install scripts and the nine user-facing clone commands in the docs use --filter=blob:none, measured against GitHub at 20 MB and 8s rather than 280 MB and 36s, keeping all 4,824 commits and every branch and tag. install-multi.sh clones inside its per-instance loop, so a five-instance install went from ~1.4 GB.

  • Sandbox: three clock boundaries resolved (7403631b, 5292719f, e4918105, f0b39cf1, c3e568ea, #1789, #1801, #1808) - sandbox_positions.updated_at is written by func.now(), which on SQLite is UTC, and get_open_positions compared it against a boundary built from naive local wall time. On an IST host the boundary landed 5:30:00 late, dropping open positions last updated between 03:00 and 08:30 IST from the position book, which is what a polling strategy reads to decide whether it is flat. The same shape affected the T+1 settlement cutoff, pushing a CNC position created between 00:00 and 05:30 IST into settlement a day early. as_db_utc() now exists in one place, since this file has had the same bug twice. Separately, a SESSION_EXPIRY_TIME of '25:00' parses cleanly as an int and then raises on replace(), outside the try, and catch_up_mis_squareoff() swallows it, so a config typo silently disabled a risk control. And two same-symbol fills arriving together each read the same quantity and wrote their own delta, so the second overwrote the first with both orders still showing complete; the regression test fires several concurrent pairs because a single pair only interleaves about two thirds of the time.

  • Holdings returned no LTP or average price (5bd4cd6a, d36936a6, #1917, #1919) - Angel ignored averageprice and ltp, and Zerodha read last_price to derive the P&L percentage and then threw it away. The holdings page rendered a dash in both columns, the CSV export wrote them empty, and portfolio analytics weighted by a zero investment value. Both brokers also failed the whole request on one bad row: Angel reports an empty demat as holdings: null rather than [], so an account with nothing bought got an HTTP 500 instead of an empty table, and a single Zerodha holding with a null average_price, last_price or pnl turned the entire request into a 500 rather than degrading that one row.

  • Flattrade reported a funded pledge as no pledge (3ca1adfa, #1936) - the Limits response carries several collateral figures and the wrong one was read. brkcollamt is the pre-valued collateral amount and sits at 0.00 on an ordinary pledged account, while the pledged value lives in collateral, which is what the Flattrade app labels "Holdings Collateral". The reported account held 9,385.34 cash and 1,890.30 of pledged NSE collateral; OpenAlgo showed the cash correctly and collateral 0.00. Shoonya and Zebu share this Noren API and carry the same line, left alone here pending confirmation against a live pledged account.

  • CORS_ENABLED=FALSE opted the deployment into the permissive policy (c88b5c8e, #1860, #1848) - get_cors_config() returned an empty dict when disabled and flask-cors then applied its own defaults, which set origins="*". Measured against the previous behaviour, a GET to /api/v1/funds carrying Origin: https://evil.example returned that origin back, and an OPTIONS preflight for a JSON order POST returned 200 allowing x-api-key. That let a cross-origin page place an order through the user's own instance with a leaked API key, from the user's broker-registered IP. The same applied to any .env predating the variable. The enabled-but-unconfigured case now fails closed too.

  • TradeSmart quotes were throttled to a fraction of the allowance (b8e4cbdd, #1928) - quotes are metered separately by the broker, so pacing them against the shared 10/sec and 120/min per-user budget was wrong. /GetQuotes now reserves from its own 100/sec bucket with no per-minute ceiling, and the REST quote fan-out pool sizes to that rather than to the general constant, which had capped throughput at 8/sec regardless. Orders, funds, margin and history keep the shared gate. Placed orders are also tagged openalgo in the Noren remarks field, which the OMS echoes back in the order book.

  • Option resolver validation (0c41679d, #1829) - the strike interval was used as a divisor with no guard, so 0 raised ZeroDivisionError and a negative, NaN or infinite value produced a strike that formats into a symbol no exchange lists, both surfacing as HTTP 500. An unrecognised option type was silently priced as a put: every offset calculation branches on CE and takes the else branch for everything else, so "CALL", "C" or None resolved to the put strike with no error and a log line saying it was correct. 145 test cases; neutering the two validators fails 91 of them.

  • Auth: no way out of a half-finished login (cab45dc9, 51ea6013) - the broker-auth pages render outside Layout and therefore have no navbar, and POST /auth/login redirects back to /broker whenever a user is already in the session, so someone who completed the password login but not the broker login had no UI path out. The only escapes were typing /auth/logout by hand or clearing cookies. The confirm dialog is shown unconditionally rather than signing out on the first click, because OpenAlgo allows concurrent devices sharing one broker feed and logout() takes the full teardown path across every device. Password login also clears the session before writing any authenticated value, so a reset token or a half-finished TOTP park cannot survive underneath.

  • CI now tests what the project advertises (a551845b, #1894) - pyproject declares requires-python >=3.12 and the frontend declares three Node majors, but CI pinned one point in each range. backend-test now runs on Python 3.12, 3.13 and 3.14 and the frontend jobs on Node 20, 22 and 24, with fail-fast off. All 166 pinned dependencies resolve to an identical set on all three Python versions, so this is coverage rather than a porting effort. Note that matrix jobs report as backend-test (3.12), so a required status check list must use the expanded names.

  • Test isolation was decided by import order (4852d463) - conftest assigns DATABASE_URL to the test databases, then utils/config.py calls load_dotenv(override=True) at import and puts the real paths back. Whether that happened depended entirely on which module a given test imported first. The visible result was seven Flow workflows from the QA regression suite appearing in the operator's Flow Editor, named "retention test", "age test", "mine", "theirs", removable only by hand. Verified before and after: a full run put seven workflows in db/openalgo.db before and none after.

  • A silent feed logged a warning every two minutes (3f2fa11e) - a subscribed feed is legitimately silent every night, every weekend and every trading holiday, and the check cannot tell that apart from a dead socket without an exchange-aware calendar. A Saturday afternoon logged an identical line forty times in ninety minutes, counting up to "no ticks for 4716s". Moved to debug; get_adapter_health() still reports seconds since the last tick per user.

  • Smaller fixes - the frontend rate limiter retained a timestamp exactly windowMs old, so waiting exactly as long as the module reported was not enough (78e4e490, #1830). Multi-option Greeks batch state is keyed by leg index (0f99ab6c, #1819). Client error reports no longer persist a reset-password token or a broker OAuth code into log/errors.jsonl (e9b29821, 07a5f649, #1851). get_history() rejects an unsupported source at the entry point (c654d85e, #1826) and the market calendar helpers return 400 rather than 500 for a non-string date (242f80d4, #1824). Navigation links declare aria-current="page", which no link in the app did (1d4cade3, #1833). The dead zmq==0.0.0 stub, a placeholder package shipping no code, is dropped from all three dependency lists (4fa997dc, #1895). Ten boot-time lines that state an expected fact moved from INFO to debug (5fd89238). The security audit's own NullPool check was failing on the documentation warning against the defect (301fe95a).

  • Docs and skills - broker plugin counts synchronised to 36 across 17 files plus the FAQ, the devsprint guide and the design docs, with historical snapshots deliberately left alone (eb7d25c2, 122dbed5, #1844, #1906, #1910). A documentation-only contribution workflow (287e8d02, #1846), a completed CONTRIBUTING table of contents (447786fa, #1883), contributor test commands aligned with what CI actually runs (1d6ce13f, a32cb277, #1841), corrected frontend build-artifact and Node guidance (6d3a0d08, #1842) and obsolete /react routes replaced with /dashboard (7e409e49, #1840). Five claims in the Flow import format were proved wrong by the docs rewrite and corrected (6ed6442d), including marketHoursOnly defaulting to false in the executor while the editor writes true, and days never being read by the daily branch, so a "weekdays only" daily schedule still fired at the weekend. Three new skills: chart-indicator (f4359718), flow-builder (cb954658) and verify (262abe06), the last encoding six rules each earned from a specific failure in this cycle rather than invented.


Dependencies

  • openalgo-charts: 1.6.0 to 1.8.2 (eleven input types, ohlc plot groups, draws, background and bar colours, instrument tick on the calculation context, and the stop-limit drag fix that had been sending price: 0 to the broker)
  • zmq==0.0.0 removed from pyproject.toml, requirements.txt and requirements-nginx.txt. It is a placeholder package on PyPI shipping no code; the zmq module every call site imports comes from pyzmq, which is already pinned.

No other Python dependencies changed. The pinned openalgo SDK stays at 2.0.3.


Contributors

  • @marketcalls (Rajandran) - release management; the eventlet boundary sweep (#1402, #1473, #1569) and its regression suites; the four-pass broker credential redaction sweep and the SensitiveDataFilter Bearer defect; Dhan master contract, symbol construction, NCO segment and unsubscribe fixes (#1929, #1930, #1932, #1934); runtime-loaded user chart indicators (#1923); the charting terminal from openalgo-charts 1.6.0 to 1.8.2, replay, settings dialog, chart sync, history cache, undo/redo and the indicator browser; Flow node-contract audit across all 61 types, payload-driven order fields, clock-anchored interval schedules and the market-hours window; the sandbox session-boundary and T+1 clock fixes; Flattrade collateral (#1936); repository size work (#1896, #1897, #1898); Motilal Oswal WebSocket pooling and leak fixes; CI matrix across Python 3.12-3.14 and Node 20/22/24 (#1894); test database isolation; the chart-indicator, flow-builder and verify skills.
  • @Kalaiviswa - Motilal Oswal plugin repair and modernisation (#1912); GTT for Angel One, Fyers and Upstox (#1922); Angel and Zerodha holdings LTP, average price and null-row resilience (#1917, #1919); Dhan unsubscribe and master contract PRs (#1932, #1934); TradeSmart order tagging and the 100/sec quote budget (#1928); Flow MCX commodity options, leg-by-leg multi-leg baskets and NRML defaults on derivative segments (#1904, #1909); the Shoonya wrong-instrument quote guard.
  • @santhiprakash (Santhi Prakash) - sandbox position-book session boundary in the database clock (#1789); MIS square-off by updated_at session boundary (#1801); concurrent position update serialization (#1808); dropped unused market_data secondary indexes in Historify (#1803).
  • @siddharthg2309 (Siddharth Gouthaman) - history source allowlist at the service entry point (#1875); client error report URL sanitization on both boundaries (#1886); aria-current on navigation links (#1880); option chain view mode toggle coverage (#1876).
  • @solstxce - API key redaction in core service logs (#1914); credential removal from broker logs (#1916).
  • @nightcityblade - client errors for invalid calendar dates (#1861); completed CONTRIBUTING table of contents (#1883).
  • @WilliamK112 (Ching Wei Kang) - documentation-only contribution workflow (#1907); rate limiter expiry at the window boundary (#1868).
  • @ANONYMOUSZED-beep (Arun) - CORS_ENABLED=FALSE honoured instead of falling through to the flask-cors wildcard (#1860).
  • @Narasimha722 (NarasimhaReddy) - strike interval and option type validation at the option resolver boundary (#1829).
  • @Pragitics (Pragit R V) - multi-option Greeks batch state keyed by leg index (#1885).
  • @Meraj-08 (Md Meraj Alam) - eliminated contradictory hard-coded broker plugin counts (#1906).
  • @K-PRAGALATHAN (PRAGALATHAN K) - converted the history format reconnaissance script into hermetic pytest coverage (#1887).
  • @NavadeepDj (NavadeepDJ) - type hints and a 37-case suite for the data schema validators (#1864).
  • @suhaslord (Suhas) - contributor test commands aligned with CI (#1889).
  • @thaildhe172591 (Luu Thai) - corrected frontend build-artifact and Node guidance in CONTRIBUTING (#1863).
  • @yiheng-kkk - replaced obsolete /react frontend routes in CONTRIBUTING (#1867).
  • @PadmaBalajiL (Padma Balaji Leelavinodhan) - devsprint participants list (#1814).
  • @cracker314 - devsprint participants list (#1817).

Thank you to everyone who filed an issue, reproduced a defect or reviewed a pull request this cycle. The eventlet work in particular rests on three separate reports (#1402, #1473, #1569) filed months apart by users who had every reason to believe the problem was on their own machine.


Links

Don't miss a new openalgo release

NewReleases is sending notifications on new releases.