fix: keep HTX spot frozen balance in the owned base inventory - #246
Merged
brokermr810 merged 2 commits intoSep 16, 2026
Merged
brokermr810 merged 2 commits into
brokermr810 merged 2 commits into
Conversation
HTX /v1/account/accounts/{id}/balance returns one row per (currency, type):
a "trade" row for the sellable part and a "frozen" row for whatever is
locked by resting orders. get_spot_base_holding returned on the first row
matching the base asset, so the frozen quantity never reached the holding.
get_spot_total_base_balance documents that ownership must use the whole
account inventory, precisely because open limit orders move quantity from
available to locked without changing ownership. With a resting sell order
on HTX spot, the total came back short and the drift check saw a position
that is not actually gone. Row order is not guaranteed either, so a
"frozen"-first payload reported the locked amount as available.
Accumulate over every row for the base asset instead: "frozen" adds to
the total only, anything else adds to both the total and the available
part.
Only trade balances are sellable; keep frozen balances in owned trading inventory and exclude unrelated balance types. Cover non-trading rows in both orders, close sizing, and fully frozen holdings.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
While reading
get_spot_base_holdingI noticed the HTX branch returns as soon as it finds a row whosecurrencymatches the base asset. HTX'sGET /v1/account/accounts/{id}/balancedoes not return one row per currency — it returns one row per(currency, type): atraderow for the sellable part and a separatefrozenrow for whatever is locked by resting orders. So only the first of the two rows ever reached the holding.That matters because of the contract the same file states:
get_spot_total_base_balancesays ownership and drift checks must use the whole account inventory, "open limit orders can move quantity fromavailabletolockedwithout changing ownership, so using the sellable balance here would create a false negative drift".position_query.pyrepeats it for spot. On HTX that is exactly what happens: with 0.6 BTC free and 0.4 BTC sitting in a resting sell order, the total came back as 0.6 instead of 1.0, and the drift check sees a position that has not actually gone anywhere.The row order is not guaranteed either. If the
frozenrow comes first, the locked quantity was reported as both the total and the available balance, so the close-sizing clamp inclamp_spot_close_quantitywould size against quantity that cannot be sold.Binance already gets this right (
free + locked), and there is a test pinning it —test_spot_ownership_total_includes_locked_while_close_uses_free. This brings HTX in line.Changes
spot_sizing.get_spot_base_holding: accumulate over every row matching the base asset instead of returning on the first one. Afrozenrow adds to the total only; anything else adds to both the total and the available part.tests/test_spot_sizing.py, mirroring the existing Binance one:trade+frozenin the documented order, and the reversed order.Test plan
Both new tests fail on
mainand pass with the change:After:
Whole backend suite, plus the two checks
basic-ciruns:API documentation (if routes/schemas changed)
No route or schema change, so nothing to regenerate.
Found by a defect-hunting pipeline I build and run (Dev-next-gen), using Claude Code with Anthropic's Claude Opus 5.