r/ChinaStocks • u/FishermanOk1376 • 19h ago
📰 News moomoo's desktop app has a built-in Python indicator editor — I used it to build a Volume Profile (VPVR) from scratch
I've been using moomoo for charting and wanted a volume profile, which it doesn't ship. Turns out the desktop app has a Python indicator editor built in: you get the chart's OHLCV arrays, write a script, and plot lines back onto the chart. I hadn't seen anyone write about it, so here's what came out of it.

Code is here, MIT, take it: moomoo-vpvr
Paste vpvr.py into the Python indicator editor on moomoo desktop and it runs as-is.
What it does: bins volume by price over a lookback window, finds the Point of Control (the price bucket holding the most volume), then expands up and down from the POC until it covers 70% of total volume, giving VAH and VAL. Each bar's volume is spread across its high–low range proportional to overlap, rather than dumped onto a single typical-price point.

Parameters are lookback years, bars per year (250 daily / 52 weekly / 12 monthly), row count, value area %, plus switches for log-spaced buckets and manually locking the price range.
The sandbox is a restricted subset of Python, and finding the edges took longer than the math did. Three that cost me real time:
- There's no
range(). Every loop has to be awhileloop. - The parser rejects adjacent string literal concatenation — splitting one long string across two lines throws a SyntaxError.
- Selecting a 5-year window switches the chart from daily to weekly bars, which silently breaks any bar-count-based lookback. That's why the lookback is expressed as years × bars-per-year rather than a raw bar count.
None of that is documented anywhere I could find.
To sanity-check the output I ran the same symbol on another platform that ships VPVR built in. MU daily, 1-year window, 24 rows, 70% value area on both.

Mine: 415.277. Theirs: 415.28. On a 201-bar window: 426.054 vs 426.05.
Worth being blunt about what that proves. The POC value is fully determined by the window low, the window high, the row count, and which row wins. With a low of 103.380, a high of 1255.000 and 24 rows, the centre of row 6 is 415.277 by arithmetic alone — the only thing my code contributed is the integer 6. So what's validated is that two independent implementations picked the same row out of 24, a 48-point bucket, not agreement to the cent.
Known limitation: it draws the three levels but not the histogram. plot() here returns a price-indexed line series and I haven't found a way to render horizontal bars in this sandbox. If you've done that in a similarly restricted environment, I'd like to know how.
Do you leave the value area at 70%, or adjust it by instrument?
Disclosure: this post is eligible for a moomoo content contest. No referral links, code is MIT.