8000
Skip to content

margin update - #151

Open
dpy1123 wants to merge 3 commits into
exchange-core:masterfrom
dpy1123:margin
Open

margin update#151
dpy1123 wants to merge 3 commits into
exchange-core:masterfrom
dpy1123:margin

Conversation

@dpy1123
@dpy1123 dpy1123 commented Apr 2, 2025
Copy link
Copy Markdown

changes:
1.in handleMatcherEventMargin, split updatePositionForMarginTrade into 2 function, for calculate pnl in partial close
2.in BALANCE_ADJUSTMENT case, check the lockedMargin
3.add checkAndLiquidateAllPositions

return false;
}

private void checkAndLiquidateAllPositions(OrderCommand cmd) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i assume this is going to be called after every price tick that is different than previous tick... you can clearly see why margin support kills performance of most exchanges, still you can get away with this for some time until you have large enough user base or active enough market to choke the core down...

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact, according to the design of exchange-core, there will be an L2 market data inserted into the orderCommand every 10ms. We can use this as a basis

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you point where in the code you see that? that looks more like a timer logic ,so that core has something to process at least every 10 ms, thats how you do concept of time in deterministic systems, you make sure some event with timestamp is issued frequently enough for your logic to process things... You cant do system.millis or nanos thats not deterministic. Matching engine shouldnt grab some L2 market data, it is the one issuing it.

@dpy1123 dpy1123 Apr 4, 2025
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In MatchingEngineRouter#L264, will fetch L2 market data when cmd.serviceFlags !=0, and In GroupingProcessor#L240 will at the end set cmd.serviceFlags to 1 every 10ms.
so our original idea is to reuse this mechanism to trigger liquidation check, but now we don't need to do that.

if (position.isEmpty()) {
removePositionRecord(position, userProfile);
}
log.debug("Liquidated: uid={} symbol={} size={} price={} pnl={}", userProfile.uid, position.symbol, sizeToLiquidate, price,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Someone being liquidated should mean appropriate market orders are issued to reduce their exposure to 0 asap... maybe i need to check out the code in the ide but since i do not see something similar to issuing new OrderCommands i assume you dont do it... you cant just liquidate someone by changing some numbers in internal state. Market order must go out, match with some other one or more orders and then it can be called "liquidated such and such"

@dpy1123 dpy1123 Apr 2, 2025
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you're right, I was just following the post process in place order, like the updatePositionForMarginTrade function...
I'll try to emit an opposite place order cmd(should marked as type liquidated? so that it has highest priorty), and re-submit to exchange-api.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes something like that, you need to use your own core as a customer, usually this liquidation logic and risk calculations are separate to the core because its insanely more compute than what core does, risk follows what each position is going trough, marks most risky ones and as soon as they cross some safety tresholds starts issuing market orders on behalf of that customer to the core, which executes them (core shouldnt care or know which orders are for what) its sole purpose is to match them asap... you can see how much trouble exchange core dev went trough to save few object allocations here and there, all that unreadable code is just to be fast.

So i would discourage putting any stream api or any object allocating logic in there, it usually belongs in a separate process.


private void checkAndLiquidateAllPositions(OrderCommand cmd) {
userProfileService.getAllUserProfiles()
.filter(up -> uidForThisHandler(up.uid))
@balayanv balayanv Apr 2, 2025
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this entire method just checking for one user or all? if its one user then its definitely really bad idea to iterate over alll users just to filter one... this should be done with O(1) operation... i cant see implementation of uidForThisHandler but its poorly named, it must return a boolean since its in the filter but from the name i have 0 idea what it actually does...

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all user, every market data change,may be cause use liquidate

8000

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok then fundamentally its correct but i think its obvious that performance will be shit )
no one does this in the same process as matching, but as proof of concept you can prototype here and then worry how to make it work with external process on a separate core..

.filter(up -> uidForThisHandler(up.uid))
.filter(up -> !up.positions.isEmpty())
.forEach(userProfile -> {
userProfile.positions.stream().forEach(position -> {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

forget using stream api in performance critical code like this, you are creating a load of objects... all that disruptor magic to avoid object allocation means nothing if you do things like this in critical path

@dpy1123
dpy1123 commented Apr 4, 2025
Copy link
Copy Markdown
Author

changes since last review:
1. triggering liquidation via order:
• liquidation is triggered by creating a reverse IOC order.
• Introduce a new API endpoint: ApiLiquidationOrder.
2. decouple liquidation check from risk engine:
• the liquidation check no longer directly modifies the user’s position.
• move this logic out of the RiskEngine.
• introduce a new component: LiquidationScanner, responsible for scanning and identifying liquidation candidates.

@balayanv appreciate your review on this version, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

0