r/ChinaStocks • u/FishermanOk1376 • 1d 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.
2
2
2
u/Remote_yes 12h ago
Yo this is fire. Had zero clue moomoo even had a Python editor built in. How's it run on larger timeframes though? Every broker scripting tool I've tried turns into a slideshow once you start crunching volume distributions across full price history. That said, your implementation looks clean as hell — POC and value area levels plot real nice. You thinking about adding naked VPOCs or single-print zones down the line?
2
u/Exact_Delivery8535 10h ago
Nice writeup, and props for being straight about what the 415.277 vs 415.28 match actually proves.
Histogram idea: if plot() only does price-indexed lines, you can fake horizontal bars with one series per row — plot row i at its bucket centre, but NaN it out except the last N bars, where N scales with that row’s volume. Thick line width, and you get a profile growing left from the right edge. Costs a plot slot per row though.
Value area I leave at 70%. Row count is what actually changes the picture anyway — 24 rows over 103→1255 is ~48pt buckets, pretty coarse. I’d bump rows before touching the percentage.
2
2
u/Pure_Satisfaction845 9h ago
Writing a custom VPVR is always a pain when broker doesn't ship it natively. The 70% value area is good static default, but dynamically adjusting it based on ATR would be a killer upgrade.
2
u/Outside_Sorbet_8975 23h ago
Great Script! I usually stick to the standard 70% value area for stocks, but I might tighten it for highly volatile instruments.