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_lockand the Flow executor's tickdata_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 fromutils/real_threading, which resolves to the unpatched originals under eventlet and the stdlib otherwise. TheEventneeded more than a swap: a greenEventnever wakes on aset()from a real thread, and a realEvent.wait()wakes but blocks the hub, soreal_threading.wait_forpolls the flag and yields between checks.Logging handler locks and inline callback dispatch.
logging.Handlerbuilds 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.createLockis now patched on the class, so third-party handlers get a real lock too. Separately,_handle_messageran 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_timeoutwaits 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_threadsafenever wakes its caller. It hands back aconcurrent.futures.Futurewhoseresult()waits on a greenthreading.Conditionwhile 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 whysubscribe()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.pyandtest/test_sqlite_lock_cooperative.pyprove each direction under a real hub, in a subprocess becausemonkey_patch()cannot be undone, asserting on elapsed time and hub liveness rather than return values, which were always right.95d6d1farecords 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 bypassesSensitiveDataFilter. This was not merely a missing filter:websocket_proxy/base_adapter.pyalready sets a redacting logger, so every adapter subclass reassigningself.loggerin 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 thehttpx.HTTPStatusErrorwhose 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 includingX-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.cookiewas added to the alternation,utils/logging.pygained its first tests, and the frontend stopped sending mutating requests with no CSRF token whenfetchCSRFToken()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_IDper segment, not per exchange, andassign_valuesmatched 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, andget_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-decimalSEM_CUSTOM_SYMBOL, which misformatted 1,628 NFO contracts as.50CEand, 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, sincemaster_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_sandboxwas 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_sandboxalso 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.jsfile instrategies/indicators/and it becomes an indicator in the/tradingpicker, 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, becausefrontend/dist/is built by CI from what is committed and user indicators are deliberately gitignored, so a bundled one would be erased by the nextgit pull. The folder sits understrategies/to mirrorstrategies/scripts/, which also puts it inside theopenalgo_strategiesDocker 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 notbars.lengthlong, and a plot keycalcnever 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 anohlcgroup 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.0resolves 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_positionmatched 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 asApiKeycaused 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 freshBrokerDataper 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 mirroringtest_aliceblue_ws_reuse.py. -
GTT for Angel One, Fyers and Upstox (
6d7f2552, #1922) - extends GTT beyond Zerodha and Dhan. Each broker registers itself by shippingapi/gtt_api.pyplusmapping/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 oforderInfo.leg2and 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,positionCheckandfundCheckread the broker response without checking its status, so a 401 gave LTP 0.0 andstatus: 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 placeholderFalse, 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.timeWindowcould 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-modeunsubscribewith no symbol fell through tounsubscribe_all, clearing the subscriptions the sandbox engine uses to trigger pending SL and LIMIT orders. Two injection paths inhttpRequest: 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 justsymbol: 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 byFLOW_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=Oksnapshot 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 onNIFTY18AUG2624600CEfilled at 24391.25, the NIFTY spot, against an LTP of 39.85. All fiveGetQuotespaths now compare the echoed exch/token against the request and retry up to three times; the two multiquotes fetchers bypassedget_api_responseentirely 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.brand.gznext 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 MBfrontend/distoccupies 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. Trackeddistfiles 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.shclones 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_atis written byfunc.now(), which on SQLite is UTC, andget_open_positionscompared 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, aSESSION_EXPIRY_TIMEof'25:00'parses cleanly as an int and then raises onreplace(), outside the try, andcatch_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 ignoredaveragepriceandltp, and Zerodha readlast_priceto 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 asholdings: nullrather than[], so an account with nothing bought got an HTTP 500 instead of an empty table, and a single Zerodha holding with a nullaverage_price,last_priceorpnlturned 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.brkcollamtis the pre-valued collateral amount and sits at 0.00 on an ordinary pledged account, while the pledged value lives incollateral, 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 setorigins="*". Measured against the previous behaviour, a GET to/api/v1/fundscarryingOrigin: https://evil.examplereturned that origin back, and an OPTIONS preflight for a JSON order POST returned 200 allowingx-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.envpredating 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./GetQuotesnow 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 taggedopenalgoin the Norenremarksfield, 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 raisedZeroDivisionErrorand 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" orNoneresolved 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 outsideLayoutand therefore have no navbar, andPOST /auth/loginredirects back to/brokerwhenever 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/logoutby 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 andlogout()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) -pyprojectdeclaresrequires-python >=3.12and the frontend declares three Node majors, but CI pinned one point in each range.backend-testnow runs on Python 3.12, 3.13 and 3.14 and the frontend jobs on Node 20, 22 and 24, withfail-fastoff. 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 asbackend-test (3.12), so a required status check list must use the expanded names. -
Test isolation was decided by import order (
4852d463) -conftestassignsDATABASE_URLto the test databases, thenutils/config.pycallsload_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 indb/openalgo.dbbefore 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
windowMsold, 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 intolog/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 declarearia-current="page", which no link in the app did (1d4cade3, #1833). The deadzmq==0.0.0stub, 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/reactroutes replaced with/dashboard(7e409e49, #1840). Five claims in the Flow import format were proved wrong by the docs rewrite and corrected (6ed6442d), includingmarketHoursOnlydefaulting to false in the executor while the editor writes true, anddaysnever being read by thedailybranch, so a "weekdays only" daily schedule still fired at the weekend. Three new skills:chart-indicator(f4359718),flow-builder(cb954658) andverify(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,ohlcplot groups, draws, background and bar colours, instrument tick on the calculation context, and the stop-limit drag fix that had been sendingprice: 0to the broker)zmq==0.0.0removed frompyproject.toml,requirements.txtandrequirements-nginx.txt. It is a placeholder package on PyPI shipping no code; thezmqmodule every call site imports comes frompyzmq, 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
SensitiveDataFilterBearer defect; Dhan master contract, symbol construction, NCO segment and unsubscribe fixes (#1929, #1930, #1932, #1934); runtime-loaded user chart indicators (#1923); the charting terminal fromopenalgo-charts1.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; thechart-indicator,flow-builderandverifyskills. - @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_atsession boundary (#1801); concurrent position update serialization (#1808); dropped unusedmarket_datasecondary 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-currenton 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=FALSEhonoured 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
/reactfrontend 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
- Repository: https://github.com/marketcalls/openalgo
- Documentation: https://docs.openalgo.in
- Python SDK on PyPI: https://pypi.org/project/openalgo/
- Discord: https://www.openalgo.in/discord
- YouTube: https://www.youtube.com/@openalgo
- Issue tracker: https://github.com/marketcalls/openalgo/issues