DEV Community

Cover image for Windows has a free OCR engine. I pointed it at my broker's position grid.
Khushal Jain
Khushal Jain

Posted on

Windows has a free OCR engine. I pointed it at my broker's position grid.

Every options trader I know keeps their book in an Excel sheet they update by hand. Strikes, quantities,
premiums, and a row of "what if NIFTY is here at expiry" columns that they retype every time the market moves.
The sheet is the honest part of the process - it is the only place where the size of the risk is written down.
And it is also the part nobody wants to maintain.

So I built a tool around one idea: the screenshot your broker already shows you is the input format.

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

The repo is nifty-desk (MIT, Python, Windows). This post is about the
two parts that were actually hard: reading the screenshot, and getting the numbers to stay live.

The trick nobody tells you about Windows OCR

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

Here is the real call from ocr/winocr.ps1:

Add-Type -AssemblyName System.Runtime.WindowsRuntime
Add-Type -AssemblyName System.Drawing

[Windows.Media.Ocr.OcrEngine,Windows.Foundation.UniversalApiContract,ContentType=WindowsRuntime] | Out-Null
[Windows.Graphics.Imaging.BitmapDecoder,Windows.Foundation.UniversalApiContract,ContentType=WindowsRuntime] | Out-Null

$engine = $null
$langs = @([Windows.Media.Ocr.OcrEngine]::AvailableRecognizerLanguages)
$pick = $langs | Where-Object { $_.LanguageTag -eq $LanguageTag } | Select-Object -First 1
if ($null -eq $pick -and $langs.Count -gt 0) { $pick = $langs[0] }
if ($null -ne $pick) { $engine = [Windows.Media.Ocr.OcrEngine]::TryCreateFromLanguage($pick) }
Enter fullscreen mode Exit fullscreen mode

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

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

if ($Scale -ne 1.0) {
  $src = [System.Drawing.Image]::FromFile($Path)
  $dst = New-Object System.Drawing.Bitmap ([int]($src.Width * $Scale)), ([int]($src.Height * $Scale))
  $g = [System.Drawing.Graphics]::FromImage($dst)
  $g.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic
  $g.DrawImage($src, 0, 0, $dst.Width, $dst.Height)
}
Enter fullscreen mode Exit fullscreen mode

Twenty-two lines of PowerShell replaced what would otherwise be a cloud OCR bill and a privacy problem.

Parsing: read the header, never the column position

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

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

  • CE/PE recovery. 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.
  • Lot size auto-detection. 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.

Then there is the sign convention, which is the part that quietly ruins P&L sheets:

The terminal shows a short as a negative Net Qty. The workbook writes a SELL as a positive Qty with a
Side column, and the scenario grid reads sell-positive / buy-negative so the formulas come out right.

That convention is stated in the README and printed in the sheet, because the alternative is a book that looks
correct and is wrong by twice the position.

Keeping it honest: live prices, not a snapshot

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

The output is one block per expiry, laid out like the sheet a person would have made by hand:

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

This is real generated output, not a mockup:

generated workbook

The line I care about most is the CP IF grid. It answers "what happens to my book if the market sits exactly
where it is - or moves 500 points against me"
before the market answers it for you.

Shipping a .exe to people who do not have Python

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

Two things I got pushed on, and handled deliberately:

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

"It read 0 rows." Silent failure is the worst outcome for a parsing tool, so the activity log now names the
cause:

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

Each message maps to an action. Debugging became a checkbox instead of a support conversation.

Run it

No-Python path: grab NiftyDesk-win64-v1.0.4.zip from the
latest release, unzip, double-click NiftyDesk.exe.
Workbooks land in an output folder next to it.

From source:

pip install -r requirements.txt
python server.py     # http://127.0.0.1:8765/
Enter fullscreen mode Exit fullscreen mode

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

What I would do differently

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

Which broker's position grid do you fight with every week - and what does your Excel sheet refuse to tell you
about your own risk? If you want the workbook itself, the repo is
85599/nifty-desk. It is MIT, so fork it, break it, or hand me a
screenshot layout it chokes on.

Top comments (0)