<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Khushal Jain</title>
    <description>The latest articles on DEV Community by Khushal Jain (@khushaljain023).</description>
    <link>https://dev.to/khushaljain023</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2342024%2Ff7324eca-2f9d-4809-a40a-431f853fa96c.png</url>
      <title>DEV Community: Khushal Jain</title>
      <link>https://dev.to/khushaljain023</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/khushaljain023"/>
    <language>en</language>
    <item>
      <title>98 stars across 26 repos taught me that engineering effort and GitHub stars are unrelated</title>
      <dc:creator>Khushal Jain</dc:creator>
      <pubDate>Thu, 29 Oct 2026 08:44:56 +0000</pubDate>
      <link>https://dev.to/khushaljain023/98-stars-across-26-repos-taught-me-that-engineering-effort-and-github-stars-are-unrelated-1j9f</link>
      <guid>https://dev.to/khushaljain023/98-stars-across-26-repos-taught-me-that-engineering-effort-and-github-stars-are-unrelated-1j9f</guid>
      <description>&lt;p&gt;I have been building open-source tools for Indian markets under&lt;br&gt;
&lt;a href="https://github.com/85599" rel="noopener noreferrer"&gt;github.com/85599&lt;/a&gt; since 2021. Last week I pulled the actual numbers instead of the&lt;br&gt;
impression I had in my head:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;26&lt;/strong&gt; public repos, &lt;strong&gt;98&lt;/strong&gt; stars, &lt;strong&gt;28&lt;/strong&gt; forks, &lt;strong&gt;21&lt;/strong&gt; followers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;21&lt;/strong&gt; of 26 had a description; &lt;strong&gt;21&lt;/strong&gt; had a license; &lt;strong&gt;16&lt;/strong&gt; had topics, &lt;strong&gt;10&lt;/strong&gt; had none&lt;/li&gt;
&lt;li&gt;CI runs on 4 of my 6 flagship repos. Two do not.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the ranking was a slap:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Repo&lt;/th&gt;
&lt;th&gt;Stars&lt;/th&gt;
&lt;th&gt;Forks&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;BankNIFTY-Golden-Ratio-Strategy&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;12&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;a ~10-line README describing one rule&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;KJStockScreener&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Streamlit screener, live on Streamlit Cloud&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pinesprout&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;a Pine Script CLI toolkit with its own lexer, parser, formatter, linter, v4→v6 upgrader, on PyPI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Strike &amp;amp; Hedge&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;NSE+BSE chains, Greeks, PCR, max pain, rule engine, optional multi-agent desk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Research-Copilot-Pro&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;analyst → bull/bear → risk → PM agent pipeline for Indian equities&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The repo I spent the least time on has the most stars, and the one I am proudest of as &lt;em&gt;software&lt;/em&gt; has a third&lt;br&gt;
of them. Here is what I think that actually means, plus the levers that did work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 1: a rule stated plainly beats a tool nobody can evaluate in 10 seconds
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;BankNIFTY-Golden-Ratio-Strategy&lt;/code&gt; is this, nearly verbatim: golden number = &lt;code&gt;((prev high - prev low) + first&lt;br&gt;
10-minute opening range) * 61.8%&lt;/code&gt;, buy above prev close + that, sell below prev close - that, 0.5% stop, 2%&lt;br&gt;
target. Its own README says &lt;em&gt;"Its a bad but popular strategy that makes decent returns."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I wrote that line honestly and it is the reason people trust the rest. That repo has &lt;strong&gt;12 forks against 18&lt;br&gt;
stars&lt;/strong&gt; - an absurd ratio, and it means people are taking the rule and coding it themselves rather than&lt;br&gt;
starring and leaving. The engineering-heavy repos get stars from people who want the binary; the one-paragraph&lt;br&gt;
repo gets forks from people who want the idea.&lt;/p&gt;

&lt;p&gt;If you only maintain one thing, consider maintaining the &lt;em&gt;idea&lt;/em&gt; in a form someone can copy in ten seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lesson 2: my topics were noise, not search terms
&lt;/h2&gt;

&lt;p&gt;Here is a nice way to check whether your own metadata is doing anything: ask what someone would type into&lt;br&gt;
GitHub search to find your repo, and compare it to the tags you actually set.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;pinesprout&lt;/code&gt; - a command-line linter, formatter and v4-to-v6 upgrader for Pine Script - was tagged&lt;br&gt;
&lt;code&gt;tensorflow&lt;/code&gt;, &lt;code&gt;deep-learning&lt;/code&gt;, &lt;code&gt;machine-learning&lt;/code&gt;, &lt;code&gt;nlp&lt;/code&gt;, &lt;code&gt;data-science&lt;/code&gt;. It does not use TensorFlow. It does&lt;br&gt;
not use a model for the linting, formatting or upgrading at all; those are a hand-written lexer and a rule&lt;br&gt;
engine. Meanwhile &lt;code&gt;cli&lt;/code&gt;, &lt;code&gt;linter&lt;/code&gt;, &lt;code&gt;static-analysis&lt;/code&gt; and &lt;code&gt;code-formatter&lt;/code&gt; - the four words someone searching&lt;br&gt;
for exactly this tool would type - were missing.&lt;/p&gt;

&lt;p&gt;So those are set now, along with &lt;code&gt;open-interest&lt;/code&gt;, &lt;code&gt;max-pain&lt;/code&gt; and &lt;code&gt;llm-agents&lt;/code&gt; on the options desk, and&lt;br&gt;
&lt;code&gt;computer-vision&lt;/code&gt; on the chart reader. Three things I learned doing it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub caps you at 20 topics per repo.&lt;/strong&gt; &lt;code&gt;nifty-desk&lt;/code&gt; was already sitting at 20 (and its 20 are genuinely
good ones - &lt;code&gt;ocr&lt;/code&gt;, &lt;code&gt;openpyxl&lt;/code&gt;, &lt;code&gt;payoff&lt;/code&gt;, &lt;code&gt;risk-management&lt;/code&gt;), so there was nothing to add. The cap was the
problem, not the tags.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;PATCH /repos/{owner}/{repo}&lt;/code&gt; silently ignores the &lt;code&gt;topics&lt;/code&gt; field.&lt;/strong&gt; My first script "succeeded" on all six
repos and changed nothing, because topics live at their own endpoint
(&lt;code&gt;PUT /repos/{owner}/{repo}/topics&lt;/code&gt;, which &lt;em&gt;replaces&lt;/em&gt; the whole list - read it first and merge, or you will
delete everything that was there). A 200 response is not a confirmation; read the value back.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Topics and stars are independent.&lt;/strong&gt; &lt;code&gt;Trading-View&lt;/code&gt; has 5 stars and zero topics. &lt;code&gt;Strike &amp;amp; Hedge&lt;/code&gt; has 6 stars
and 20 topics. Tagging a repo is writing a search term for a stranger; it says nothing about whether anyone
wanted the thing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Lesson 3: what actually moved stars was removing friction
&lt;/h2&gt;

&lt;p&gt;The pattern across every repo that grew:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MIT, stated in the badge.&lt;/strong&gt; The 2021 ones without a license file read as "all rights reserved" and get
skipped. No license does not mean "free to use", it means "legally unclear", and a careful evaluator will
close the tab rather than guess.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"No API key needed" in the first sentence.&lt;/strong&gt; That is a data-source claim, not a feature, and for anyone
evaluating a finance tool at 11pm it is the whole decision. Both &lt;code&gt;Strike &amp;amp; Hedge&lt;/code&gt; and &lt;code&gt;optionchainpro-terminal&lt;/code&gt;
lead with it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A binary for people who do not have Python.&lt;/strong&gt; &lt;code&gt;nifty-desk&lt;/code&gt; ships a Windows exe
(&lt;code&gt;NiftyDesk-win64-v1.0.4.zip&lt;/code&gt;) precisely because the traders who wanted it were the least likely to run
&lt;code&gt;pip install&lt;/code&gt;. The install path that reaches the most people is usually not the one engineers prefer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A GIF of the real flow, and real generated output.&lt;/strong&gt; No mockups. The Excel screenshot in that README is an
actual workbook the tool wrote. This is the only "marketing" skill I have, and it does more than any
feature list.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tests that run offline.&lt;/strong&gt; 45 tests in &lt;code&gt;Strike &amp;amp; Hedge&lt;/code&gt;, 58 in &lt;code&gt;MarketMitra-AI&lt;/code&gt;, no network, no keys - so a
reviewer can prove the thing works without becoming a trader. &lt;code&gt;python -m pytest tests -q&lt;/code&gt; being a green
30-second experiment is worth more than a paragraph of claims.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Honest caveats.&lt;/strong&gt; The &lt;code&gt;Strike &amp;amp; Hedge&lt;/code&gt; README has a table of every public data source it could not find -
no intraday FINNIFTY, no futures quotes, BSE not publishing delta-OI intraday - and a Known Limits section
saying what the tool refuses to score. Every issue I got after adding it was a feature request instead of a
"this is broken" report.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Lesson 4: the graveyard is instructive too
&lt;/h2&gt;

&lt;p&gt;Ten of 26 repos still have zero topics, and the list decomposes cleanly once you look at it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Seven 2021 relics&lt;/strong&gt; - &lt;code&gt;85599.codebook.io&lt;/code&gt;, &lt;code&gt;flames.github.io&lt;/code&gt;, &lt;code&gt;real-time-chat&lt;/code&gt; (all January 2021),
&lt;code&gt;Remoting-CMD&lt;/code&gt; (February), &lt;code&gt;85599.github.io&lt;/code&gt; and &lt;code&gt;Django-based-stock-trading-platform&lt;/code&gt; (March), &lt;code&gt;Thar&lt;/code&gt;
(also 2021).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Two forks&lt;/strong&gt; (&lt;code&gt;AI-Trader&lt;/code&gt;, &lt;code&gt;nofx&lt;/code&gt;), which inherit no topics by design, so they are a non-issue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One genuine oversight&lt;/strong&gt;: &lt;code&gt;Trading-View&lt;/code&gt;, a 2026 repo of my own with 5 stars and no topics at all.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those 2021 repos are not embarrassing - 2021 me was learning to push code at all. But they drag on the&lt;br&gt;
profile: a visitor who lands on the repo list sees the last rows of a table of silence.&lt;/p&gt;

&lt;p&gt;The rot clustered, but not perfectly - which is the more honest finding. Of those ten topic-less repos, five&lt;br&gt;
had no description and two had no license GitHub could detect. Those two lists overlapped without being the&lt;br&gt;
same list, so "fix the plumbing" turned out to be per-repo work rather than one bulk operation.&lt;/p&gt;

&lt;p&gt;And one finding genuinely stung: &lt;strong&gt;the repo with the most stars - 18, plus those 12 forks - had no LICENSE&lt;br&gt;
file.&lt;/strong&gt; Which means, legally, it was all-rights-reserved, even though I wrote it to be copied. Twelve people&lt;br&gt;
forked a strategy they could not safely reuse. If you publish anything, put the license in before you tell&lt;br&gt;
anyone about it; MIT takes ten seconds and its absence quietly suppresses exactly the reuse you wanted.&lt;/p&gt;

&lt;p&gt;The worst one, though, was &lt;code&gt;Thar&lt;/code&gt; - a 2021 Java repo that GitHub has had &lt;strong&gt;blocked for &lt;code&gt;sensitive_data&lt;/code&gt; since&lt;br&gt;
August 2021&lt;/strong&gt;. No description, no license, and the file view returns a block notice. I genuinely do not&lt;br&gt;
remember what is in it. Whatever it was, it has been sitting there flagged for four years while I worried&lt;br&gt;
about whether my Pine Script linter had enough topics. Check your blocked and flagged repos first; that is the&lt;br&gt;
metadata that matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I fixed, and what is still broken
&lt;/h2&gt;

&lt;p&gt;Done, last week:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Real search-term topics merged into the flagship repos (read-then-merge, because that endpoint replaces).&lt;/li&gt;
&lt;li&gt;Descriptions added to the four old repos that had none, and MIT files on &lt;code&gt;BankNIFTY-Golden-Ratio-Strategy&lt;/code&gt;,
&lt;code&gt;Django-based-stock-trading-platform&lt;/code&gt; and &lt;code&gt;real-time-chat&lt;/code&gt;, credited to the GitHub handle rather than a
personal email. That is 24 of 26 licensed; the two that are not are a fork (which is not mine to license)
and the blocked repo below.&lt;/li&gt;
&lt;li&gt;A dev.to link in the profile README and in three flagship READMEs, so the write-ups and the code point at
each other.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Still broken, and I am saying so rather than editing this post later:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;Thar&lt;/code&gt; stays blocked until I decide to delete it - which is irreversible, so it waits for a clear head.
Twenty-four of 26 repos licensed is fine; one repo I cannot open is not something a badge can hide.&lt;/li&gt;
&lt;li&gt;CI on &lt;code&gt;Research-Copilot-Pro&lt;/code&gt; and &lt;code&gt;tradingview-chartsense&lt;/code&gt;. Four of six flagship repos have it; two do not.&lt;/li&gt;
&lt;li&gt;A hosted demo path for the repos that need one, longer READMEs for the strategy repos, and a decision on
whether each 2021 relic is archived or actually maintained.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you maintain open source in a niche where the users are not developers - finance, design, ops, healthcare -&lt;br&gt;
what has actually moved your stars? I am specifically looking for the ones that were not "write a better&lt;br&gt;
README". And if any of this is useful to you,&lt;br&gt;
&lt;a href="https://github.com/85599" rel="noopener noreferrer"&gt;the profile is here&lt;/a&gt;; every repo I own and can open is MIT, so forks, issues and&lt;br&gt;
"this exe does not run on my machine" reports are all equally welcome.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>fintech</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Vision models fake precision on charts - so I made every number carry its source</title>
      <dc:creator>Khushal Jain</dc:creator>
      <pubDate>Tue, 27 Oct 2026 05:46:32 +0000</pubDate>
      <link>https://dev.to/khushaljain023/vision-models-fake-precision-on-charts-so-i-made-every-number-carry-its-source-4a4p</link>
      <guid>https://dev.to/khushaljain023/vision-models-fake-precision-on-charts-so-i-made-every-number-carry-its-source-4a4p</guid>
      <description>&lt;p&gt;Ask a vision model to read a chart and it will tell you "RSI is at 71.4". Confident, specific, and - most of&lt;br&gt;
the time - invented. Multimodal models are very good at the &lt;em&gt;shape&lt;/em&gt; of a technical narrative and terrible at&lt;br&gt;
the &lt;em&gt;arithmetic&lt;/em&gt; behind it. They will also happily agree with whatever you pasted, because agreeing is the&lt;br&gt;
statistically safe answer.&lt;/p&gt;

&lt;p&gt;I wanted the read anyway - screenshots are the only format a chart exists in when you are mid-session - so I&lt;br&gt;
built &lt;a href="https://github.com/85599/tradingview-chartsense" rel="noopener noreferrer"&gt;ChartSense&lt;/a&gt; around one rule: &lt;strong&gt;the model never gets to&lt;br&gt;
be the source of a number it can be wrong about silently.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Local-first Next.js, MIT, your API key only ever goes to the provider you choose. Here are the three mechanics&lt;br&gt;
that enforce that rule.&lt;/p&gt;
&lt;h2&gt;
  
  
  1. Every value carries its provenance
&lt;/h2&gt;

&lt;p&gt;Each reading in the output is tagged:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;calculated&lt;/code&gt; - computed by the app from candles you pasted&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;read&lt;/code&gt; - actually legible off the screenshot (the axis labels, the legend, the LTP)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;estimated&lt;/code&gt; - interpolated by the model, and always printed with a &lt;code&gt;~&lt;/code&gt; so you cannot mistake it for a reading&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;no basis&lt;/code&gt; - the model wanted to say something and had nothing to say it from&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last category is the uncomfortable one, and it is the reason this is not just a prompt wrapper. A model&lt;br&gt;
that is allowed to output &lt;code&gt;no basis&lt;/code&gt; produces noticeably fewer confident fabrications, because the schema gives&lt;br&gt;
it an honest exit. Here is a trimmed real output from a Gemini read of a NIFTY 50 5-minute chart:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NIFTY 50 · 5m · LTP 22395.0
Verdict: Strong Sell (TradingView gauge 15/100) · Signal clarity 78/100 — how clean the read is, not the odds of profit

Nifty 50 is in a firm intraday downtrend after breaking below the 22474.7 breakdown level. Price continues to
print lower highs and lower lows with all key moving averages sloping overhead.

• Support: 22355.5 and 22305.6
• Resistance: 22474.7, 22576.7 and 22719.9

   +15 Moving average alignment: Price is trading well below all estimated short and intermediate moving averages.
   +15 Trend strength (ADX): ADX is at 40.28 with DI- dominant at 27.4, indicating a strong downtrend.
    -5 Oscillator agreement: RSI has dipped into oversold territory (28.25), warning of potential short-term pullbacks.
   -10 Signal freshness: The sell signal originated at 22583.85 and the move is already quite extended.

• Trigger: Rejection on a retest of 22440.0 - 22474.7  • Invalidation: 22500.0  • Target: 22355.5  • RR: 2.3:1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the second clause on the clarity line: &lt;strong&gt;how clean the read is, not the odds of profit.&lt;/strong&gt; A score that&lt;br&gt;
looks like a probability will get treated like one.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. Paste candles, and the maths stops being AI
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;lib/indicators.ts&lt;/code&gt; is plain TypeScript: SMA, EMA, RSI, MACD, Stochastic, ADX and ATR. When you paste OHLCV&lt;br&gt;
data alongside the screenshot, those get computed from the data and &lt;strong&gt;overwrite whatever the model guessed.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So the same chart produces two different quality of answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;screenshot only → levels and readings are &lt;code&gt;read&lt;/code&gt; or &lt;code&gt;estimated&lt;/code&gt;, and the output says so&lt;/li&gt;
&lt;li&gt;screenshot + candles → the moving averages and oscillators become &lt;code&gt;calculated&lt;/code&gt;, and the narrative is pinned
to numbers you can re-derive yourself&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the cheapest reliability trick I know for anything vision-model-shaped: let the model do what it is&lt;br&gt;
good at (describe structure, name the trend, assemble a plan) and take away the keyboard for anything with an&lt;br&gt;
equals sign.&lt;/p&gt;
&lt;h2&gt;
  
  
  3. The confidence score is code, not vibes
&lt;/h2&gt;

&lt;p&gt;If the model is allowed to return its own confidence, it returns what you would like to hear. So the field it&lt;br&gt;
returns is only a suggestion - the server recomputes it from the factor list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;analysis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;confidenceFactors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;summed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;analysis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;confidenceFactors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;factor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;factor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;points&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;analysis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;confidence&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;clampNumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;summed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;85&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;analysis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;confidence&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Base 50. Capped contributions per factor. De-duplicated by label, so a model that lists "strong trend" three&lt;br&gt;
times in different words does not get paid three times:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;confidence&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;factors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;
  &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;clampNumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;factors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;factor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;factor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;points&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;85&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;analysis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;confidence&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;analysis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;confidence&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the clamp is the part I would defend hardest: &lt;strong&gt;5 to 85, never higher.&lt;/strong&gt; One screenshot can never be&lt;br&gt;
certain, and a score that can reach 100 trains people to stop reading the reasons. Every individual factor is&lt;br&gt;
bounded too - e.g. oscillator agreement:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;points&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;clampNumber&lt;/span&gt;&lt;span class="p"&gt;(((&lt;/span&gt;&lt;span class="nx"&gt;agree&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;against&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;agree&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;against&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same idea runs the gauge: &lt;code&gt;clampNumber(50 + lean * 50, 0, 100, 50)&lt;/code&gt;, and if nothing scored, it returns&lt;br&gt;
&lt;code&gt;{ index: 50, verdict: "neutral" }&lt;/code&gt; instead of guessing a direction. Neutrality is a real answer, and code that&lt;br&gt;
has no path to it will lie to get out of it.&lt;/p&gt;

&lt;p&gt;There is also an audit trail by construction: &lt;code&gt;confidenceReason&lt;/code&gt; prints the arithmetic - how many factors went&lt;br&gt;
into the base of 50, how many of them the app scored from your candles, and which factor was strongest and&lt;br&gt;
weakest. If you disagree with a number, you can find the line that produced it.&lt;/p&gt;

&lt;p&gt;No news. No fundamentals. No predictions. Levels come from the chart or your data, and every plan ends with&lt;br&gt;
&lt;em&gt;wait for clear price confirmation&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run it locally (the security shape)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install
&lt;/span&gt;npm run dev      &lt;span class="c"&gt;# http://localhost:3000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Click &lt;strong&gt;Connect AI keys&lt;/strong&gt;, paste one key - Grok, OpenAI, Gemini or OpenRouter - hit &lt;strong&gt;Test key&lt;/strong&gt;, then &lt;strong&gt;Save&lt;br&gt;
keys&lt;/strong&gt;. No key yet? Demo mode still runs the real indicator maths on candles you paste.&lt;/p&gt;

&lt;p&gt;Three deliberate decisions, since this is a tool that holds an API key:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;keys are stored in &lt;code&gt;~/.chartsense&lt;/code&gt;, &lt;strong&gt;outside&lt;/strong&gt; the project folder - copying or publishing the source can
never carry one&lt;/li&gt;
&lt;li&gt;localhost is open by design; &lt;strong&gt;any other host is refused&lt;/strong&gt; until you set &lt;code&gt;CHARTSENSE_ACCESS_TOKEN&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;every route has per-IP rate limits plus body, image and input validation (details in &lt;code&gt;SECURITY.md&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Local-first also means the privacy claim is structural rather than promotional: there is no account, no&lt;br&gt;
telemetry, and no server of mine. Your screenshot goes to the provider you keyed in, and nowhere else.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it still gets wrong
&lt;/h2&gt;

&lt;p&gt;It is a single-screenshot read: no multi-timeframe confluence beyond what is visible, and no tick data, so a&lt;br&gt;
level that would need the order book to validate is not something this can give you. The &lt;code&gt;estimated&lt;/code&gt; tag is&lt;br&gt;
honest, but honest wrong numbers are still wrong numbers - which is exactly why pasting candles changes the&lt;br&gt;
quality of the answer so much.&lt;/p&gt;

&lt;p&gt;Where do you draw the line between letting a model interpret and letting it compute - and has anyone actually&lt;br&gt;
gotten a vision model to read a chart's axis scale reliably without feeding it the underlying data?&lt;br&gt;
Repo: &lt;a href="https://github.com/85599/tradingview-chartsense" rel="noopener noreferrer"&gt;85599/tradingview-chartsense&lt;/a&gt;, MIT.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>ai</category>
      <category>javascript</category>
      <category>tradingview</category>
    </item>
    <item>
      <title>There is no official grammar for Pine Script, so I wrote a small one</title>
      <dc:creator>Khushal Jain</dc:creator>
      <pubDate>Mon, 26 Oct 2026 07:17:55 +0000</pubDate>
      <link>https://dev.to/khushaljain023/there-is-no-official-grammar-for-pine-script-so-i-wrote-a-small-one-3eh3</link>
      <guid>https://dev.to/khushaljain023/there-is-no-official-grammar-for-pine-script-so-i-wrote-a-small-one-3eh3</guid>
      <description>&lt;p&gt;If you have ever tried to lint, format, or diff TradingView Pine Script programmatically, you have hit the same&lt;br&gt;
wall: &lt;strong&gt;there is no official grammar.&lt;/strong&gt; TradingView does not publish or maintain a tree-sitter grammar or an&lt;br&gt;
ANTLR grammar for Pine Script. Every parser you can find is third-party, and third-party grammars for a&lt;br&gt;
language that changes with each Pine version go stale quietly - they just stop matching real-world code.&lt;/p&gt;

&lt;p&gt;I could not find anything to put in a CI pipeline for my indicators, so I wrote&lt;br&gt;
&lt;a href="https://github.com/85599/pinesprout" rel="noopener noreferrer"&gt;PineSprout&lt;/a&gt; (&lt;code&gt;pip install pinesprout&lt;/code&gt;, Python 3.12+, Linux/macOS/Windows,&lt;br&gt;
MIT). This post is about the parser decision and the linter rules, which are the two parts worth stealing.&lt;/p&gt;
&lt;h2&gt;
  
  
  The whole toolkit in one CLI
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pinesprout generate &lt;span class="s2"&gt;"&amp;lt;prompt&amp;gt;"&lt;/span&gt;     &lt;span class="c"&gt;# Claude-powered, from natural language&lt;/span&gt;
pinesprout template new ema-cross-indicator &lt;span class="nt"&gt;--title&lt;/span&gt; &lt;span class="s2"&gt;"My EMA Cross"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; my_indicator.pine
pinesprout lint my_indicator.pine
pinesprout format my_indicator.pine &lt;span class="nt"&gt;--write&lt;/span&gt;
pinesprout analyze my_indicator.pine
pinesprout optimize my_indicator.pine
pinesprout explain my_indicator.pine
pinesprout upgrade legacy_strategy.pine &lt;span class="nt"&gt;--to&lt;/span&gt; 6 &lt;span class="nt"&gt;--write&lt;/span&gt;
pinesprout docs my_indicator.pine &lt;span class="nt"&gt;--format&lt;/span&gt; markdown &lt;span class="nt"&gt;-o&lt;/span&gt; DOCS.md
pinesprout readme my_indicator.pine &lt;span class="nt"&gt;-o&lt;/span&gt; README.md
pinesprout report my_indicator.pine
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Every command supports &lt;code&gt;--json&lt;/code&gt;. That is not a nicety - it is the reason the tool is usable outside a terminal.&lt;br&gt;
Machine-readable output means the linter can gate a PR, the formatter can run in a pre-commit hook, and the&lt;br&gt;
upgrader can be scripted across a folder of old v4 scripts.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why a hand-rolled lexer instead of tree-sitter
&lt;/h2&gt;

&lt;p&gt;Two options existed: depend on an unofficial grammar, or write a small one. I wrote the small one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pinesprout/core/
├── lexer.py        # regex-based Pine Script tokenizer
├── parser.py       # lightweight AST builder
├── ast_nodes.py    # typed AST node definitions
├── formatter.py    # deterministic code formatter
├── linter.py       # static analysis rule engine
├── analyzer.py     # structural / complexity analysis
├── optimizer.py    # refactor suggestions
├── upgrader.py     # v4 -&amp;gt; v5 -&amp;gt; v6 migrations
└── version_rules.py# deprecated symbols &amp;amp; migration rules
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The deliberate constraint: the output shape &lt;strong&gt;mirrors a tree-sitter concrete syntax tree&lt;/strong&gt; - typed nodes,&lt;br&gt;
line/column spans, a &lt;code&gt;children&lt;/code&gt; list. Nothing downstream (formatter, linter, analyzer, optimizer, upgrader)&lt;br&gt;
knows or cares how the tree got built.&lt;/p&gt;

&lt;p&gt;That buys two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It handles real-world Pine's edge cases without waiting on a third party's release cycle.&lt;/li&gt;
&lt;li&gt;If TradingView ever ships an official grammar, it drops in behind &lt;code&gt;parser.py&lt;/code&gt; and &lt;strong&gt;no consumer changes.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A regex tokenizer is not "proper" compiler design, and I am fine with that. It is ~two files, dependency-free,&lt;br&gt;
well tested, and it removes the only thing in the project I could not control.&lt;/p&gt;
&lt;h2&gt;
  
  
  The lint rule that pays for itself: repaint risk
&lt;/h2&gt;

&lt;p&gt;Pine's most expensive class of bug is not a syntax error, it is an indicator that looks brilliant historically&lt;br&gt;
and lies in real time. Repainting. So the linter has a dedicated category (&lt;code&gt;repaint-risk&lt;/code&gt;) driven by an actual&lt;br&gt;
list in &lt;code&gt;version_rules.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;REPAINT_PRONE_FUNCTIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;request.security&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;security&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ta.valuewhen&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ta.barssince&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input.source&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;request.security()&lt;/code&gt; on a higher timeframe without a lookahead guard, &lt;code&gt;ta.valuewhen()&lt;/code&gt; reading a value that has&lt;br&gt;
not settled on the real-time bar - when one of these shows up in a plot path, the linter flags the line and&lt;br&gt;
explains that the script can repaint historical values on lower timeframes, with a separate note for the case&lt;br&gt;
where the repainting is intentional. There is a matching &lt;code&gt;PERFORMANCE_SENSITIVE_FUNCTIONS&lt;/code&gt; set (&lt;code&gt;ta.sma&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;ta.ema&lt;/code&gt;, &lt;code&gt;ta.rsi&lt;/code&gt;, &lt;code&gt;ta.atr&lt;/code&gt;, &lt;code&gt;ta.stoch&lt;/code&gt;, &lt;code&gt;ta.macd&lt;/code&gt;, &lt;code&gt;ta.highest&lt;/code&gt;, &lt;code&gt;ta.lowest&lt;/code&gt;) because calling a rolling&lt;br&gt;
series inside a loop is the second most common way to make a script crawl.&lt;/p&gt;

&lt;p&gt;Alongside those, the rule set covers unused variables, deprecated syntax, and general performance issues - the&lt;br&gt;
four things that actually make an indicator script unmaintainable. Because the rules are a rule &lt;em&gt;engine&lt;/em&gt;, not a&lt;br&gt;
pile of regexes, you can add your house style without touching the core:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .pinesprout/plugins/my_rules.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pinesprout.plugins.base&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PineSproutPlugin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LintRule&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pinesprout.core.linter&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LintIssue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Severity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LintCategory&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NoMagicColorsRule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LintRule&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no-magic-colors&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;LintIssue&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;issues&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;splitlines&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;color=color.red&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;LintIssue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;severity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Severity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INFO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;LintCategory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;STYLE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Avoid hard-coded colors; expose as an input.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;issues&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyPlugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PineSproutPlugin&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-org-rules&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.0.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lint_rules&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;NoMagicColorsRule&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;

&lt;span class="n"&gt;PLUGIN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MyPlugin&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Plugins load from a local &lt;code&gt;.pinesprout/plugins/&lt;/code&gt; folder or through a &lt;code&gt;pinesprout.plugins&lt;/code&gt; entry point, so a team&lt;br&gt;
can version its own rule pack as a package.&lt;/p&gt;
&lt;h2&gt;
  
  
  Upgrading v4 → v5 → v6
&lt;/h2&gt;

&lt;p&gt;Pine's version migrations are mostly mechanical renames, and they live as data rather than as if-statements in&lt;br&gt;
the upgrader:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# `study()` was renamed to `indicator()` in Pine v5.
# `security()` moved to the `request.*` namespace in v5.   -&amp;gt; request.security(
# `strategy.performance` -&amp;gt; strategy.closedtrades.* accessors introduced in v6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A regex rewrite table plus typed &lt;code&gt;DeprecatedSymbol&lt;/code&gt; entries is honestly enough for a language whose breaking&lt;br&gt;
changes are namespace moves. &lt;code&gt;upgrade --to 6 --write&lt;/code&gt; applies them, and it is the command I reach for most,&lt;br&gt;
because a v4 script you still believe in is worth more than a rewrite you will never start.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Studio, if you prefer a browser
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;streamlit_app.py&lt;/code&gt; wraps the CLI: describe a strategy in plain English (AI-generated), pick a template, or paste&lt;br&gt;
your own script, then lint / format / analyze / optimize / explain / upgrade / document it with one-click&lt;br&gt;
downloads.&lt;/p&gt;

&lt;p&gt;The bit that is genuinely fun is the Template Builder: &lt;strong&gt;25 indicators&lt;/strong&gt; (trend, momentum, volatility, volume) ×&lt;br&gt;
&lt;strong&gt;4 signal patterns&lt;/strong&gt; × &lt;strong&gt;3 risk-management styles&lt;/strong&gt;, with every length, threshold and stop-loss % adjustable -&lt;br&gt;
thousands of distinct scripts, all of them lint-clean because they come out of the same formatter the CLI uses.&lt;br&gt;
There is also a live scrolling ticker (NIFTY 50, SENSEX, BANK NIFTY, commodities via yfinance) and an NSE-style&lt;br&gt;
index watch plus a TradingView global heatmap for the market-browsing side.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;".[dev]"&lt;/span&gt;
streamlit run streamlit_app.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Guardrails worth stating
&lt;/h2&gt;

&lt;p&gt;PineSprout only ever operates on Pine Script source &lt;strong&gt;you provide&lt;/strong&gt;. It does not fetch, decompile, or bypass&lt;br&gt;
protection on TradingView's invite-only or protected scripts. Anyone building tooling in this space should say&lt;br&gt;
that out loud in the README rather than let people assume - it is the difference between a useful tool and a&lt;br&gt;
ToS incident.&lt;/p&gt;

&lt;p&gt;Dev loop is &lt;code&gt;pytest&lt;/code&gt;, &lt;code&gt;ruff check .&lt;/code&gt;, &lt;code&gt;mypy pinesprout&lt;/code&gt;, and CI runs on every push.&lt;/p&gt;

&lt;p&gt;What's the buggiest pattern in your Pine scripts - repainting on historical bars, or &lt;code&gt;security()&lt;/code&gt; look-ahead?&lt;br&gt;
And if you maintain a TradingView repo, do you run any lint/format gate on &lt;code&gt;.pine&lt;/code&gt; files today? The toolkit is&lt;br&gt;
&lt;a href="https://github.com/85599/pinesprout" rel="noopener noreferrer"&gt;85599/pinesprout&lt;/a&gt; on PyPI and GitHub, MIT - the parser is the part I would&lt;br&gt;
most like someone to disagree with.&lt;/p&gt;

</description>
      <category>python</category>
      <category>opensource</category>
      <category>tradingview</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Windows has a free OCR engine. I pointed it at my broker's position grid.</title>
      <dc:creator>Khushal Jain</dc:creator>
      <pubDate>Mon, 26 Oct 2026 07:02:16 +0000</pubDate>
      <link>https://dev.to/khushaljain023/windows-has-a-free-ocr-engine-i-pointed-it-at-my-brokers-position-grid-2ih1</link>
      <guid>https://dev.to/khushaljain023/windows-has-a-free-ocr-engine-i-pointed-it-at-my-brokers-position-grid-2ih1</guid>
      <description>&lt;p&gt;Every options trader I know keeps their book in an Excel sheet they update by hand. Strikes, quantities,&lt;br&gt;
premiums, and a row of "what if NIFTY is here at expiry" columns that they retype every time the market moves.&lt;br&gt;
The sheet is the honest part of the process - it is the only place where the size of the risk is written down.&lt;br&gt;
And it is also the part nobody wants to maintain.&lt;/p&gt;

&lt;p&gt;So I built a tool around one idea: &lt;strong&gt;the screenshot your broker already shows you is the input format.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Drop it in, get back a workbook with live premiums, live P&amp;amp;L, and a scenario grid that recalculates itself.&lt;br&gt;
No broker login, no API key, and the screenshot never leaves the machine.&lt;/p&gt;

&lt;p&gt;The repo is &lt;a href="https://github.com/85599/nifty-desk" rel="noopener noreferrer"&gt;nifty-desk&lt;/a&gt; (MIT, Python, Windows). This post is about the&lt;br&gt;
two parts that were actually hard: reading the screenshot, and getting the numbers to stay live.&lt;/p&gt;
&lt;h2&gt;
  
  
  The trick nobody tells you about Windows OCR
&lt;/h2&gt;

&lt;p&gt;Windows ships an OCR engine - &lt;code&gt;Windows.Media.Ocr&lt;/code&gt;, the same one the Photos app uses. It is free, it runs&lt;br&gt;
locally, it has recognizers per language, and it needs no install. The catch is that it is a WinRT API, so&lt;br&gt;
the practical way to reach it from Python is to shell out to PowerShell.&lt;/p&gt;

&lt;p&gt;Here is the real call from &lt;code&gt;ocr/winocr.ps1&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Add-Type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-AssemblyName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;System.Runtime.WindowsRuntime&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Add-Type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-AssemblyName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;System.Drawing&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Windows.Media.Ocr.OcrEngine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;Windows.Foundation.UniversalApiContract&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;ContentType&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;WindowsRuntime&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Out-Null&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Windows.Graphics.Imaging.BitmapDecoder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;Windows.Foundation.UniversalApiContract&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;ContentType&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;WindowsRuntime&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Out-Null&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nv"&gt;$engine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$null&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$langs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@([&lt;/span&gt;&lt;span class="n"&gt;Windows.Media.Ocr.OcrEngine&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;::AvailableRecognizerLanguages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$pick&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$langs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Where-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LanguageTag&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$LanguageTag&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Select-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-First&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;$null&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$pick&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$langs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-gt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$pick&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$langs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;$null&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-ne&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$pick&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$engine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Windows.Media.Ocr.OcrEngine&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;TryCreateFromLanguage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$pick&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;AsTask&lt;/code&gt; + a small &lt;code&gt;Await&lt;/code&gt; helper turns the WinRT async calls into something a script can wait on - that is&lt;br&gt;
the only way to drive WinRT from Windows PowerShell without writing a compiled app.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Upscale before you recognise.&lt;/strong&gt; This was the single biggest accuracy win. The ODIN terminal draws its&lt;br&gt;
position grid in a roughly 9px font. At 1:1 the engine silently drops characters, and a dropped character in&lt;br&gt;
a strike price is not a typo - it is a different trade. So the image goes through a high-quality bicubic&lt;br&gt;
resize at 2x first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$Scale&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-ne&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nv"&gt;$src&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;System.Drawing.Image&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;FromFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$Path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nv"&gt;$dst&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;New-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;System.Drawing.Bitmap&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;int&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="nv"&gt;$src&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Width&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$Scale&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;int&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="nv"&gt;$src&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Height&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$Scale&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nv"&gt;$g&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;System.Drawing.Graphics&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;FromImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$dst&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nv"&gt;$g&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InterpolationMode&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;System.Drawing.Drawing2D.InterpolationMode&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;HighQualityBicubic&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nv"&gt;$g&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DrawImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$dst&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$dst&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Height&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Twenty-two lines of PowerShell replaced what would otherwise be a cloud OCR bill and a privacy problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Parsing: read the header, never the column position
&lt;/h2&gt;

&lt;p&gt;A grid is not a text file. My first instinct was to split each row on whitespace and index into it, which&lt;br&gt;
works exactly once, on my screen, on my broker layout.&lt;/p&gt;

&lt;p&gt;The version that ships reads the &lt;strong&gt;header row&lt;/strong&gt; and only uses the columns that header actually spells out.&lt;br&gt;
If a broker hides three columns, Net Qty, Market Price and MTM still land in the right cells, and nothing is&lt;br&gt;
guessed at. Two other things earned their complexity:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CE/PE recovery.&lt;/strong&gt; When OCR loses the option type on a leg, recover it from the sibling leg of the same
strike, or from the live NSE price for that instrument. Do not fail the whole row for one dropped glyph.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lot size auto-detection.&lt;/strong&gt; Quantities in the grid are in lots; the workbook needs contracts. The tool
infers the lot size from the quantity values instead of asking you to type it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then there is the sign convention, which is the part that quietly ruins P&amp;amp;L sheets:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The terminal shows a short as a &lt;em&gt;negative&lt;/em&gt; Net Qty. The workbook writes a SELL as a &lt;strong&gt;positive&lt;/strong&gt; Qty with a&lt;br&gt;
&lt;code&gt;Side&lt;/code&gt; column, and the scenario grid reads sell-positive / buy-negative so the formulas come out right.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That convention is stated in the README and printed in the sheet, because the alternative is a book that looks&lt;br&gt;
correct and is wrong by twice the position.&lt;/p&gt;
&lt;h2&gt;
  
  
  Keeping it honest: live prices, not a snapshot
&lt;/h2&gt;

&lt;p&gt;A parsed screenshot goes stale in about four minutes. So each workbook pulls premiums and spot from the public&lt;br&gt;
NSE option chain - no key, no subscription - and refreshes every 5 minutes.&lt;/p&gt;

&lt;p&gt;The output is one block per expiry, laid out like the sheet a person would have made by hand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a full &lt;code&gt;STRIKE / QTY / PREMIUM&lt;/code&gt; ladder at a 50-point gap, &lt;strong&gt;including the zero rows&lt;/strong&gt;, so you can type an
unstated position straight into the sheet&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CP IF &amp;lt;spot&amp;gt;&lt;/code&gt; scenario columns at a 100-point gap, each one a live &lt;code&gt;IF&lt;/code&gt; formula - edit a quantity or a rate
and the whole grid recalculates&lt;/li&gt;
&lt;li&gt;CE section, PE section, and a &lt;code&gt;NIFTY&lt;/code&gt; futures line that is always present even when the screenshot has no
future, so you can add one yourself&lt;/li&gt;
&lt;li&gt;a grand &lt;code&gt;TOTAL&lt;/code&gt;, with each expiry printing on its own page&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is real generated output, not a mockup:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxj88ttwc1ri7t1kgr70e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxj88ttwc1ri7t1kgr70e.png" alt="generated workbook" width="800" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The line I care about most is the &lt;code&gt;CP IF&lt;/code&gt; grid. It answers &lt;em&gt;"what happens to my book if the market sits exactly&lt;br&gt;
where it is - or moves 500 points against me"&lt;/em&gt; before the market answers it for you.&lt;/p&gt;
&lt;h2&gt;
  
  
  Shipping a .exe to people who do not have Python
&lt;/h2&gt;

&lt;p&gt;The traders who wanted this are the least likely to run &lt;code&gt;pip install&lt;/code&gt;. So there is a Windows binary built with&lt;br&gt;
PyInstaller (&lt;code&gt;build_exe.bat&lt;/code&gt; in the repo does it in one command if you would rather build it yourself).&lt;/p&gt;

&lt;p&gt;Two things I got pushed on, and handled deliberately:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Windows protected your PC."&lt;/strong&gt; That is SmartScreen, because the exe is unsigned - code-signing certificates&lt;br&gt;
cost money. The README says exactly that, tells you to build from source if you prefer, and the whole source is&lt;br&gt;
right there. I would rather explain the warning than pay for a certificate I cannot honestly make mean more&lt;br&gt;
than it does.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"It read 0 rows."&lt;/strong&gt; Silent failure is the worst outcome for a parsing tool, so the activity log now names the&lt;br&gt;
cause:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;"none of them formed the ODIN position grid"&lt;/em&gt; - header row was cropped out of the screenshot&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;"does not follow the standard ODIN column layout"&lt;/em&gt; - columns rearranged or hidden in that terminal&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;"no text at all"&lt;/em&gt; - the English OCR recognizer is not installed (Settings → Time &amp;amp; Language → Language)&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;"every row was filtered out"&lt;/em&gt; - grid parsed fine, untick &lt;em&gt;NIFTY rows only&lt;/em&gt; or &lt;em&gt;Hide fully closed&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each message maps to an action. Debugging became a checkbox instead of a support conversation.&lt;/p&gt;
&lt;h2&gt;
  
  
  Run it
&lt;/h2&gt;

&lt;p&gt;No-Python path: grab &lt;code&gt;NiftyDesk-win64-v1.0.4.zip&lt;/code&gt; from the&lt;br&gt;
&lt;a href="https://github.com/85599/nifty-desk/releases/latest" rel="noopener noreferrer"&gt;latest release&lt;/a&gt;, unzip, double-click &lt;code&gt;NiftyDesk.exe&lt;/code&gt;.&lt;br&gt;
Workbooks land in an &lt;code&gt;output&lt;/code&gt; folder next to it.&lt;/p&gt;

&lt;p&gt;From source:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
python server.py     &lt;span class="c"&gt;# http://127.0.0.1:8765/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Privacy note, stated in the code's shape rather than a policy page: the only network request the tool makes is&lt;br&gt;
the public NSE quote call. Positions and screenshots stay local. And it is a risk-visibility and bookkeeping&lt;br&gt;
tool - it places no orders, promises no returns, and is not investment advice.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would do differently
&lt;/h2&gt;

&lt;p&gt;Wire the same pipeline to the other popular terminals' layouts (right now it is built around ODIN's grid), and&lt;br&gt;
push the scenario grid to options other than NIFTY, since the parsing is symbol-agnostic but the defaults are not.&lt;/p&gt;

&lt;p&gt;Which broker's position grid do you fight with every week - and what does your Excel sheet refuse to tell you&lt;br&gt;
about your own risk? If you want the workbook itself, the repo is&lt;br&gt;
&lt;a href="https://github.com/85599/nifty-desk" rel="noopener noreferrer"&gt;85599/nifty-desk&lt;/a&gt;. It is MIT, so fork it, break it, or hand me a&lt;br&gt;
screenshot layout it chokes on.&lt;/p&gt;

</description>
      <category>python</category>
      <category>opensource</category>
      <category>fintech</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>No API key, no data vendor: pulling live NSE + BSE option chains in Python</title>
      <dc:creator>Khushal Jain</dc:creator>
      <pubDate>Mon, 26 Oct 2026 07:00:55 +0000</pubDate>
      <link>https://dev.to/khushaljain023/no-api-key-no-data-vendor-pulling-live-nse-bse-option-chains-in-python-4fe0</link>
      <guid>https://dev.to/khushaljain023/no-api-key-no-data-vendor-pulling-live-nse-bse-option-chains-in-python-4fe0</guid>
      <description>&lt;p&gt;Retail option analytics in India has a data problem. Licensed feeds cost real money, brokers gate their API&lt;br&gt;
behind accounts, and the free tools that exist hand you a wall of numbers and stop there. Meanwhile the&lt;br&gt;
exchanges publish option chains on their own public websites - which anyone can read, and which nobody will&lt;br&gt;
support you when it changes.&lt;/p&gt;

&lt;p&gt;I built &lt;a href="https://github.com/85599/Strike-and-Hedge" rel="noopener noreferrer"&gt;Strike &amp;amp; Hedge&lt;/a&gt; on exactly those public endpoints: live&lt;br&gt;
NSE &lt;strong&gt;and&lt;/strong&gt; BSE chains, Black-Scholes Greeks solved locally, PCR, max pain, OI walls, expected move, and a&lt;br&gt;
rule-based trade plan. No API key. MIT. 45 offline tests.&lt;/p&gt;

&lt;p&gt;This post is the field notes - the parts that are not in any tutorial because they only show up when a&lt;br&gt;
government exchange quietly retires an endpoint on a Tuesday.&lt;/p&gt;
&lt;h2&gt;
  
  
  The anti-bot wall comes first
&lt;/h2&gt;

&lt;p&gt;Both exchanges sit behind Akamai. Plain &lt;code&gt;requests&lt;/code&gt; gets 403s, and no amount of header cosplay fixes it,&lt;br&gt;
because the TLS handshake itself is the fingerprint. What works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# copilot/nse.py, copilot/bse.py
# curl_cffi impersonates a real Chrome TLS/JA3 profile; warm cookies from the homepage first
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;curl_cffi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;   &lt;span class="c1"&gt;# falls back to `requests` if curl_cffi is unavailable
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep the fallback. &lt;code&gt;curl_cffi&lt;/code&gt; is a compiled dependency and it will not install on some machine you care&lt;br&gt;
about; the tool should degrade to "this source did not load" rather than "this tool does not start".&lt;/p&gt;
&lt;h2&gt;
  
  
  Endpoints are a moving target, so write down what moved
&lt;/h2&gt;

&lt;p&gt;The specific things that bit me, as of October 2026:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Need&lt;/th&gt;
&lt;th&gt;What is actually there&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;NSE chains&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;option-chain-v3&lt;/code&gt; + &lt;code&gt;option-chain-contract-info&lt;/code&gt;. The old &lt;code&gt;option-chain-indices&lt;/code&gt; endpoint is &lt;strong&gt;retired&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BSE (SENSEX) chain&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;DerivOptionChain&lt;/code&gt;, which wants the expiry formatted as &lt;code&gt;15 Oct 2026&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BSE expiry list&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;not published.&lt;/strong&gt; Upcoming Thursdays get probed until one answers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NSE futures quotes&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;gone.&lt;/strong&gt; &lt;code&gt;quote-derivative&lt;/code&gt;, &lt;code&gt;quote-v2&lt;/code&gt; and &lt;code&gt;last-price-v5&lt;/code&gt; all 404&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FINNIFTY daily history&lt;/td&gt;
&lt;td&gt;niftyindices.com - Yahoo delisted &lt;code&gt;^CNXFINANCE&lt;/code&gt;, and NSE's own chart API returns empty for every index&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FINNIFTY intraday&lt;/td&gt;
&lt;td&gt;no public source exists&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two of those are lies rather than gaps, and they are the dangerous kind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;BSE reports &lt;code&gt;Absolute_Change_OI&lt;/code&gt; as 0 intraday.&lt;/strong&gt; Not "flat" - &lt;em&gt;not published yet&lt;/em&gt;. Rendered as a real
number, it tells you writers are not active when the exchange simply has not said.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BSE sends no IV.&lt;/strong&gt; So IV gets solved from the premium. And when a deep-ITM quote will not solve, the row
shows &lt;code&gt;—&lt;/code&gt;. A solver forced to always answer will happily hand you a 500% IV, and the Greeks downstream
will believe it.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  When a number cannot be had, derive it and label it
&lt;/h2&gt;

&lt;p&gt;With NSE's futures quote endpoints retired, a futures card has no honest price. The tool derives the futures&lt;br&gt;
&lt;em&gt;level&lt;/em&gt; from put-call parity on the ATM strike - from &lt;code&gt;copilot/chain.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;synthetic_futures&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.065&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Futures level implied by put-call parity on the ATM strike: F = K·e^(-rT) + CE - PE.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;ltp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;source&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;put-call parity on ATM strike&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then three things matter more than the formula:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;the card is &lt;strong&gt;labelled as derived&lt;/strong&gt;, and says it can be tens of points off your broker's price&lt;/li&gt;
&lt;li&gt;the parity basis is &lt;strong&gt;never scored as a signal&lt;/strong&gt; - last-traded option quotes put that much noise into it&lt;/li&gt;
&lt;li&gt;there is &lt;strong&gt;no futures OI or buildup anywhere in the score&lt;/strong&gt;, because that data does not exist&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Same principle for the FINNIFTY intraday panel: it falls back to recent daily bars &lt;strong&gt;and says that it did.&lt;/strong&gt;&lt;br&gt;
Silent fallback is where dashboards go to die. A tool that narrates its own blind spots gets used; one that&lt;br&gt;
hides them gets believed, and then gets abandoned.&lt;/p&gt;

&lt;p&gt;And at the close: signals come from a chain snapshot, not tick data, so near 15:30 IST the settled expiry is&lt;br&gt;
dropped. Otherwise you get a ₹0.05 premium with nonsense Greeks, printed as if it were actionable.&lt;/p&gt;
&lt;h2&gt;
  
  
  Split the deterministic engine from the LLM
&lt;/h2&gt;

&lt;p&gt;The pipeline is deliberately two halves:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; NSE option-chain-v3 ─┐
 BSE DerivOptionChain ─┤→ chain.py    normalise + Black-Scholes Greeks / IV
 niftyindices.com ─────┤      ↓
 Yahoo quotes/VIX ─────┘  analytics.py   PCR · max pain · OI walls · expected move
                               ↓
                          signals.py     bias + entry / stop / target / exit / lots
                               ↓
                    dataflows.py → agents.py → graph.py    (optional AI desk)
                               ↓
                          reporting.py   markdown report + decision log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;signals.py&lt;/code&gt; needs no key at all and produces &lt;code&gt;BULLISH&lt;/code&gt; / &lt;code&gt;BEARISH&lt;/code&gt; / &lt;code&gt;RANGE&lt;/code&gt; / &lt;code&gt;AVOID&lt;/code&gt; with a score and&lt;br&gt;
written reasons, plus a concrete plan: instrument, entry, stop, target, R:R, lots, max loss in ₹, exit rule.&lt;br&gt;
Stops and targets come from ATR and OI walls &lt;strong&gt;on the underlying&lt;/strong&gt;, converted to premium through delta - so&lt;br&gt;
they react to real price geometry instead of a fixed percentage. Never naked selling: bullish → BUY CE beyond&lt;br&gt;
ATM (plus a futures long on the derived level), range → defined-risk credit spreads, sell near the OI wall,&lt;br&gt;
hedge two strikes further out.&lt;/p&gt;

&lt;p&gt;The risk model has one behaviour I am stubborn about:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Type in capital and risk % - if &lt;strong&gt;one lot&lt;/strong&gt; already risks more than your capital × risk %, the tool refuses&lt;br&gt;
to round up. It tells you the position does not fit, instead of quietly showing "1 lot".&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The AI desk (chain / volatility / flow / news analysts → bull vs bear debate → risk → portfolio manager) sits&lt;br&gt;
&lt;em&gt;on top of the same numbers&lt;/em&gt;, across 8 backends: Anthropic, OpenAI, Gemini, DeepSeek, Groq, OpenRouter,&lt;br&gt;
&lt;strong&gt;Ollama (fully local, no key)&lt;/strong&gt;, or any OpenAI-compatible endpoint. Two guards make it behave:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Grounding check.&lt;/strong&gt; The LLM is given the rule engine's numbers. If its quoted entry is far from the live
premium - the classic mistake of stating a strike as a premium - the rule-engine level is shown instead,
with a warning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decision log.&lt;/strong&gt; Every AI call is scored later against the underlying's actual move with a hit/miss flag,
and those lessons feed into later runs. An agent loop with no outcome tracking is a random number generator
with good grammar.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is the engineering ask buried in that design: keep the rule engine separate. If you only want chains,&lt;br&gt;
Greeks and signals, &lt;strong&gt;delete &lt;code&gt;agents.py&lt;/code&gt; and &lt;code&gt;graph.py&lt;/code&gt; entirely&lt;/strong&gt; and the rest still works.&lt;/p&gt;
&lt;h2&gt;
  
  
  Make failure debuggable, and make tests offline
&lt;/h2&gt;

&lt;p&gt;When a chain will not load, the answer is one request away:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://localhost:8000/api/diag/NIFTY     &lt;span class="c"&gt;# what each price source actually returned&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Endpoints change and rate limits bite without notice, so a debug endpoint that shows raw per-source responses&lt;br&gt;
turns "it's broken" into "NSE is 403ing and BSE is fine".&lt;/p&gt;

&lt;p&gt;The test suite is offline on purpose - 45 tests, no network, no keys, so CI stays green whether or not an&lt;br&gt;
exchange is having a day:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; pytest tests &lt;span class="nt"&gt;-q&lt;/span&gt;
python tests/ui_harness.py    &lt;span class="c"&gt;# full UI on synthetic data → http://127.0.0.1:8765&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That synthetic harness is the other half of the trick: it lets a reviewer see the entire UI without a broker&lt;br&gt;
account, a key, or market hours. No frontend build step either - &lt;code&gt;web/&lt;/code&gt; is plain HTML/CSS/JS served by FastAPI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/85599/Strike-and-Hedge.git
&lt;span class="nb"&gt;cd &lt;/span&gt;Strike-and-Hedge &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
uvicorn server:app &lt;span class="nt"&gt;--port&lt;/span&gt; 8000     &lt;span class="c"&gt;# open http://localhost:8000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The part you should read before you scrape
&lt;/h2&gt;

&lt;p&gt;This is a &lt;strong&gt;scraper, not a licensed feed.&lt;/strong&gt; It reads public endpoints at request time and caches briefly; it is&lt;br&gt;
not a redistribution right, and exchanges can change terms whenever they like. Lot sizes in the repo are&lt;br&gt;
fallbacks - &lt;strong&gt;SEBI revises them&lt;/strong&gt;, so verify with your broker. And publishing buy/sell calls publicly can&lt;br&gt;
require SEBI Research Analyst registration, which is worth knowing before you share output anywhere. It is an&lt;br&gt;
educational research tool, not investment advice, and not issued by a registered research analyst.&lt;/p&gt;

&lt;p&gt;Which Indian exchange endpoint has wasted the most of your afternoon - and is anyone tracking a working&lt;br&gt;
futures-quote source now that NSE's are 404ing? If you want the whole pipeline to read or break:&lt;br&gt;
&lt;a href="https://github.com/85599/Strike-and-Hedge" rel="noopener noreferrer"&gt;85599/Strike-and-Hedge&lt;/a&gt;. PRs for endpoint changes, lot sizes and&lt;br&gt;
strike steps are the ones I merge fastest.&lt;/p&gt;

</description>
      <category>python</category>
      <category>fintech</category>
      <category>opensource</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
