-
Notifications
You must be signed in to change notification settings - Fork 230
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Satisfied seller replacement, trading of zero amounts #21
Comments
the goal of a sell order is to sell the full number of tokens for sale, which may result in receiving more tokens than the original amount desired, so the remaining amount desired is not a determining factor for whether or not the sell order is still active. @dexX7 @zathras-crypto @m21 does that help? |
@marvgmail: an example: Let's assume there is an offer with an unit price of 0.1, which gets partially filled. After the partial fill, there is an amount of 0.0000'0004 units left fore sale, and due to the unit price of 0.1, the amount desired of the updated offer would be 0.0000'0004 * 0.1 = 0.0000'0000'4, which is less than the minimal tradable amount of 0.0000'0001. What should happen? Three outcomes:
The first outcome is probably not what we want, but the second neither, because this could result in a lot of 1 willet/satoshi leftovers, which are offered at an higher price, due to the rounding up. |
@dexX7 @marvgmail the remaining trade after a partial fill should be subject to the same rules as any other trade. What I mean by this is that there is nothing to stop you listing a trade with an amount desired of 1 willet, so that behaviour should not be restricted at the partial trade level either. I'm not so concerned about 'leftovers' - that was definitely an issue in DEx v1 but due to automatic order matching wouldn't those leftovers be scooped up and traded (assuming a suitable counter trade). What I'm getting at is I see it as less of an issue of a bunch of leftover open trades, and more of an issue of a bunch of 1 willet completed trades cluttering things up. Not ideal in either circumstance. Basically we should always be deriving the unit price from the original amounts, and the amount desired calculated on the fly from There has been a lot of talk (and the spec explicitly states) that the price cannot change so whilst I don't object to partial trades with amount desired of 1 willet, I do object to the unit price being changed (more than doubled since it is now 4 for sale/1 desired, thus price is 0.25). Thus in my book this rules out option 2 also (option 1 is already ruled out because it breaks the rules of allowed trades). That leaves option 3, which seems the cleanest from a math PoV (return the unsold tokens to the seller and consider the offer filled) but this still strikes me as awkward. Thinking purely in laymans terms - if I sell 12.00000000 SPT and the order is filled, when I see that only 11.99999996 SPT was actually sold I may think there is a bug. I don't have any better suggestions at the mo, and it may have to be option 3 combined with user education/appropriate interface messaging. |
No, because they are more expensive:
There are three routes to handle it in general, see the first post in OmniLayer/spec#173. The logic applied in the spec is route B: "amounts are updated based on amounts sold and amount desired adjusted based on earlier unit price", and this has the side effect of "... but user B only wanted to buy 15 MSC, not 17 MSC!", if any better-than-expected fill/trade is involved. It's probably a matter of perspective. Unfortunally the issue remains, but it's simply shifted: assuming no overpriced order is created with the leftover (case 3 from above), then the orderbook remains uncluttered, but the user ends up with that tiny amount he can't really use. In my opinion this is still the better outcome, but sort of weird nevertheless. |
@dexX7 if I understand your example, it appears you are recalculating the Logically, the amount desired is not used after the original unit price is On Tuesday, April 28, 2015, dexX7 notifications@github.com wrote:
Marv Schneider |
@marv-engine: I mentioned the updated and more expensive unit price as consequence, if the leftover amount of Given the example, what would you say is the expected outcome? |
Yeah at this stage the only viable option is case 3 - I was hoping for some inspiration on some other way to handle it but none has been forthcoming so far :( |
@dexX7 Here's another excerpt from the spec:
So, as indicated, the effective unit price in your example is now 0.25. This means the order for the remaining 0.00000004 will stay in the book until someone offers to buy 0.00000004 at a unit price of at least 0.25, or the seller modifies or cancels the order. This effect will happen whenever this type of rounding occurs. The magnitude of the effect will depend on the specific numbers. I don't like option 3 because a user may be selling in order to liquidate all his holdings of that property. Option 3 would prevent that from happening. I guess if it does happen, the user could explicitly issue another sell order at a unit price of 0.25, but why force the user to do that? If nothing else, that flow would move his sell order from the oldest at that effective price to the newest. |
Thanks @marv-engine, this helps a lot, and I noticed the example from above is no exceptional situation (in the context of the core logic), which I initially assumed, due to the bias of the current implementation without original unit price and "truncated" amount desired. The following sequence should cover this situation:
After this trading sequence, order A, B and D were filled, but C is still unmatched, and should be listed in the orderbook. |
This is the part that confuses me - so much discussion about how the unit price cannot change, but this implies that a change from a unit price of 0.1 to a unit price of 0.25 is fine? Am I missing something guys? |
Let's define: The unit price is the result of the original amount desired / original amount for sale, and should not change, while the effective unit price is the result of the actually traded amounts, or amounts to be traded.
The outcome would be to move 17 apples from Alice to Bob, in return for 17 * 0.5 = 8.5 oranges from Bob to Alice. Because they want to trade only full fruits, the number of oranges is rounded up to 9 oranges. This new effective unit price of 9/17 = ~0.529 is acceptable for both, and therefore the trade happens. |
OK, I think I see where you're going. Perhaps I just have UI work stuck in my head where I'm constantly looking at unit prices we provide to the user. In your example:
I was thinking we would thus need to display the trade before partial match as a unit price of 0.1, and the trade after partial match as 0.25, thus in my mind we were "changing the unit price". If we maintain a unit price of 0.1 in our interactions with the user I just want to make sure there are no cases where we are providing inaccurate info. |
this could be handled by the UI - e.g. indicate those matched orders where the effective unit price is different than the original unit price. |
I agree that the effective unit price should be shown in the UI, because this is what matters for the counterparty. This raises another question: let's assume prices are shown with up to 8 decimals in the UI, but the digits at positions 9+ are not 0, how would that be handled? Consider the following prices, and let's ignore the amounts up for sale for the sake of an example:
I tend to think it would be less unexpected, if all prices with "hidden" digits are rounded up to the next satoshi/willet, whether the effective price is |
I believe this issue is very close to being resolved by the currently pending/close-to-be-ready changes. |
The effective unit price is known only after a match is made, because it depends on how many tokens are remaining for sale and how many would be exchanged as a result of the matching process. As for the number of decimal digits of the unit price, the smallest unit price is 0.000000000000000000000000001084 (0.00000001 tokens desired / 9,223,372,036,854,775,807 tokens for sale). Unless I'm missing something (which is entirely possible), we shouldn't round the price in the order book, but we should show the effective unit price after a match is made. |
Not all rational numbers have a finite number of decimal digits (take I created #31 to discuss the interface, especially given that I assume there is more to discuss in this context rather sooner than later. :) |
If it's feasible, I think it would be great to only 'expose' an 8 digit price. More decimals can be used internally sure, but looking at Marv's example: |
Let's go down the rabbit hole..
The newly added assertion, to guarantee a successful exchange of tokens, is violated in an attempt to transfer 0 tokens, when C and B are matched in the trade sequence from above: #21 (comment) This is because the rounding of
After the rounding is fixed, the assertion is once again violated: B has 0.00000004 SPX @ 0.1 MSC/SPX left for sale. Because the desired amount would be less than one satoshi, the amount is ceiled to 0.00000001. Based on the desired amount of 0.00000001, and an unit price of 0.1 MSC/SPX, the amount to trade would be 0.00000010 SPX. When trying to un-reserve that amount, it fails, because only 0.00000004 SPX are reserved.
Given that the effective price does not necessarily equal the original prices, there must be a check to confirm, whether it's still an accepted price and not too expensive. If this is the case, the execution should be stopped. I started with oc-0.10-mdex-zero-amounts-dirty and the relevant commits are: |
Thanks so much for this mate - can I just ask a quick question? Things like
isn't |
Yes, but I'd like to replace it as late as possible. All the tests, i.e. the test plan and the others, don't fail, even with I added a few more assertions, and it currently fails on testnet. It would be great, if someone could double check them, because I assume it might as well be an error on my end: // before the trade
assert(0 < pold->getAmountRemaining());
assert(0 < pnew->getAmountRemaining());
assert(pnew->getProperty() != pnew->getDesProperty());
assert(pnew->getProperty() == pold->getDesProperty());
assert(pold->getProperty() == pnew->getDesProperty());
assert(pold->unitPrice() <= pnew->inversePrice());
assert(pnew->unitPrice() <= pold->inversePrice());
// inbetween, to provide the context
XDOUBLE xEffectivePrice = XDOUBLE(seller_amountGot) / XDOUBLE(buyer_amountGot);
// after the trade
assert(xEffectivePrice >= pold->unitPrice()); // << ??
assert(xEffectivePrice <= pnew->inversePrice()); // << ??
assert(0 <= seller_amountStillForSale);
assert(0 <= buyer_amountStillForSale);
// amountOffered referrs to the amount offered at the beginning of the trade
assert(seller_amountOffered == seller_amountStillForSale + buyer_amountGot);
assert(buyer_amountOffered == buyer_amountStillForSale + seller_amountGot); |
OK thanks, that makes sense.
I can indeed confirm that. I started working to clean up the UI branch and disabled the asserts temporarily to test some changes. Would it be better to hold off on the UI stuff and join you in tracing through MetaDEx assert failures? |
Thanks, I'm going through it step by step, and I roughly have an understanding of the issues. I think it's probably best to disable the assertions and focus on the UI, assuming there is still something to do. - I'll post, if I reach a dead end. :) I noticed you were using |
Testnet fully processed! An additional check was required: if (buyer_amountGot == 0) {
if (msc_debug_metadex1) file_log(
"-- stopping trade execution, because buyer has not even enough "
"tokens to purchase 1 unit\n");
++iitt;
continue;
} ... and finally replacing the floats, which was more complicated than anticipated, because I'll consolidate and cleanup everything over the next day, and then we should very, very closely double-check the logic. It's a bit unfortunate that the calculation doesn't follow the example of the spec, and sort of goes backwards. Maybe I'm going to test an alternative implementation of the core (it's within those |
Exceptional work as always my friend - with thanks :) |
There is an interesting trade, where the alternative implementation differs:
Outcome 1:
Outcome 2:
|
Yeah I actually use Example:
Without dividing by
With dividing by
|
@zathras-crypto: yes, makes sense. :) My point was just: let's put all this "somewhere" in one place, so it's easier to edit globally. I'm unfortunally away until tomorrow, but the logic seems to be correct now. Still up to do: cleanup (mostly getting rid of all the debug output), maybe optimization. Before the release the assertions should probably be removed, to prevent shutting down the whole network (if there is a violation). |
I have currently 4 trades, or trade sequences, which cover most edge cases. It would be great, if we could review the outcomes next week. If this turns out to be solid, so is the core logic. :) |
It was observed that offers, which have some units left for sale, but no more desire, are added back to the orderbook, as
seller_replacement
. This results in a faulty unit price, which causes orders, with inversed currency pair, likely to be matched against that faulty offer. Further, such matches result in a valid trade, where no tokens are actually traded.Gathered logs:
Trade sequences:
A2 ends up with
for sale = 0.00000049
anddesired = 0.00000000
:RPC output with some extra info (using
gettrade_MP
,getorderbook_MP
):Debug log, with some extra output for the desired amount:
The desired amount is calculated based on unit price and amount available, but it comes down to:
To conclude, this issue is unrelated to honoring the original unit price, but the total amount of tokens still desired, based on the original unit price, is less than one willet.
It is thinkable to avoid adding such offers back to the orderbook, which appears to resolve the initial issue, however, this introduces the need for additional adjustments, outside of the meta DEx core logic, to reflect the "satisfaction" of that order. In particular, when using
gettrade_MP
to get information about the order, the status is reported as"cancelled part filled"
. Is there anything else, which should be taken care of, or might be affected, assuming such offers were not added back to the orderbook?Ping @marv-engine (to confirm proper handling and the logic), @m21 (potential mitigation and implementation pitfalls), @zathras-crypto (for the RPC results).
The text was updated successfully, but these errors were encountered: