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
feat: Initiate the dlc channel setup from the coordinator #244
feat: Initiate the dlc channel setup from the coordinator #244
Conversation
Co-authored-by: Lucas Soriano del Pino <lucas_soriano@fastmail.com>
8eb3d24
to
ab06660
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for driving this forward, I left some comments
/// This has to correspond to our order's leverage. | ||
pub leverage: f64, | ||
/// Note, this needs to be reversed for the counter party. | ||
pub direction: Direction, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❓Do we need the Direction
here?
TLDR: Since the orderbooks communicates the execution_price
(and potentially funding rate in the future), we should not need the Direction
in this model.
In the TradeParams
model the Orderbook
would communicate the price to be used as execution_price
for both parties. If the execution_price
is know then the Direction
is not needed unless I missed something. My understanding is, that the Direction
is only needed to decide which price / funding rate is to be used.
If we can I would try to avoid adding the Direction
here; I think this was one thing that was confusing in our previous model in ItchySats - and at this point (after the execution price was decided) it should be irrelevant.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you are right, the direction
might not be needed here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm.. I've seen that it was not added in the TradeParams model, but figured it was simply forgotten.
If it's not needed, how will we be able to know if the trader wants to buy or sell?
Or in other words who will profit from a price change up or down if we don't know the direction.
client | ||
.post(format!( | ||
"http://{}/api/orderbook/orders", | ||
config::get_http_endpoint() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
config::get_http_endpoint() | |
config::get_orderbook_http_endpoint() |
I suppose this is the one for the orderbook; even if we configure it as the same initially, I would add a separate configuration entry :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we don't have that config yet on the mobile app. I am going to add it later.
|
||
let channel_id = sub_channel.channel_id; | ||
|
||
// todo: the app should validate if the offered dlc channel matches it's submitted order. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❓How do we associate incoming DLC requests with orders?
In the previous flow the client was in charge of triggering the trade
based on the orderbook reporting a match. That match was for an order, and thus the client could identify the order.
The client then triggerd the execution, and only allowed one trade execution at the time - so if multiple match events from the orderbook would have come in, the client was in charge of scheduling the execution.
Now, the coordinator is in charge of scheduling the execution. If the client submitted multiple order to the orderbook, how would the client know which order is being executed here and update the order state accordingly?
By adapting this, the order / position lifecycle updates were removed, I think we should add them again so we can find an answer to the question above :)
Possibilities:
- The client asks the coordinator what is being executed
- The coordinator communicates the order id that is being executed to the client
The client asks the orderbook what was matched-> I think this does not work, because the orderbook does not execute, and several oders could have been matched, so we run into the same problem; the orderbook does not know what's being executed by the coordinator
My conclusion is that the information what order is in execution has to come from the coordinator. We can easily validate that the trade parameters that the coordinator proposes match the order in question, so I think this is fine.
Yesterday we discussed the possibility of the coordinator communicating the order-id as part of the DLC prposal. As far as I remember this was not possible out of the box, so we will have to come up with a different way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still think that the orderbook should notify the trader as well and tell them:
hey, your order X,Y,Z have been matched, expect a DLC channel of size Q coming from coordinator c.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still think that the orderbook should notify the trader as well and tell them:
hey, your order X,Y,Z have been matched, expect a DLC channel of size Q coming from coordinator c.
We can do that, but I think we additionally need a way to get information from the coordinator about which execution (i.e. DLC) is for which order. I don't think the orderbook telling us is enough - especially for a maker that potentially has several takers trading with him at the same time. We have to be able to clearly associate one order with one DLC, and I don't think the orderbook has this information in this flow.
// in 24h | ||
expiry: Duration::from_secs(60 * 60 * 24), | ||
oracle_pk: ln_dlc::get_oracle_pubkey()?, | ||
// todo: this model should include the contract_symbol |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Either this or you make the contract_symbol
part of the route, e.g.
.../api/orderbook/orders/btcusd
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm in favor of adding it as field - this allows us a more data driven approach, where the context is clear from the data that was sent.
expiry: Duration::from_secs(60 * 60 * 24), | ||
oracle_pk: ln_dlc::get_oracle_pubkey()?, | ||
// todo: this model should include the contract_symbol | ||
let new_order = trade::NewOrder { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We will need some more information here, e.g. will need to know if this is a market order or a limit order
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is how the NewOrder
is represented on the API for the app; I think this should be complete. Likely we will need a similar representation when sending an order to the orderbook.
#[frb]
#[derive(Debug, Clone)]
pub struct NewOrder {
#[frb(non_final)]
pub leverage: f64,
#[frb(non_final)]
pub quantity: f64,
#[frb(non_final)]
pub contract_symbol: ContractSymbol,
#[frb(non_final)]
pub direction: Direction,
// Box needed for complex enum, otherwise generated Rust code complains about Default impl
// missing
#[frb(non_final)]
pub order_type: Box<OrderType>,
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what about the pub_key
of the trader?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True, the pubkey of the trader should be added. On the app it's our own so we know it internally and don't send it to the API; for the orderbook we will have to add it.
let new_order = trade::NewOrder { | ||
price: dec!(22_000), // todo: replace with actual btcusd rate price | ||
quantity: Decimal::try_from(order.quantity)?, | ||
maker_id: "".to_string(), // todo: what should be provided on the maker_id? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was just a dummy field. Imho this should be the sender's pk, e.g. if maker posts the limit order, then this is the maker's pk, if it is a market order by the trader, then this is the trader's pk.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see.. Then we should probably name it trader_id
or something.
coordinator/src/node.rs
Outdated
|
||
let margin_trader = calculate_margin( | ||
match_params.execution_price, | ||
match_params.quantity as f64, // todo: the quantity should not be f64 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What else should it be?
In BTC/USD the quantity is equal to USD value of the position. As talked in the past : it would be cool to support fraction of a cent as position size, hence this needs to be float.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fair enough, I wasn't aware of that requirement. In the past we used u64
for the quantity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What precision do we want here though? I would avoid floats as much as possible.
coordinator/src/node.rs
Outdated
let margin_trader = calculate_margin( | ||
match_params.execution_price, | ||
match_params.quantity as f64, // todo: the quantity should not be f64 | ||
trade.leverage as f64, // todo: the leverage should not be f64 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unless we want to rule out fractional leverages this can be something else, otherwise this has to be a float.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need fractional leverage for a combined position, where multiple different orders (with multiple leverages) are combined in one position for one trading pair.
This has to stay f64 from my point of view.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks for the explanation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use Decimal
instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we use
Decimal
instead?
I think that's a good idea!
Note: So far when using Decimal
, we always use float
as representation when serializing/deserializing it. It will still be better to use Decimal
when performing calculations; but just for the sake of communicating information f64
might actually be fine. We can however still use the Decimal
type here, because then it becomes an implementation detail how it is serialized / desierialized.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the orderbook I've used Decimal
in most places.
/// Emitted by the orderbook to the coordinator for execution when a match is found. | ||
#[derive(Debug, Clone, Serialize, Deserialize)] | ||
pub struct MatchParams { | ||
/// The taker trade information |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we need to be able to identify which order was matched because the trader might have multiple orders pending.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, the match params ended up in the wrong commit, the trade model (introduced in the next commit) includes the order id.
maker/src/bin/maker.rs
Outdated
let node = node.clone(); | ||
async move { | ||
loop { | ||
tokio::time::sleep(PROCESS_TRADE_REQUESTS_INTERVAL).await; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Imho the sleep should come at the end, otherwise we wait unnecessarily for the first attempt.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having the sleep in the beginning has the advantage, that the continues below will not skip the sleep.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the flow is pretty much what we discussed. One critical question remains open though: 2f2d435
(#244)
978bdee
to
614c3ad
Compare
614c3ad
to
af0ccaf
Compare
This way we will get a reliable pub key of the maker.
Setup gets a bit complex at this point as the maker is now also included in the test. We need to find a way to do this automated as there a so many moving parts currently. Manuell testing failed with either the taker or the maker channel getting closed during dlc channel proposal. see message below. ``` 2023-03-10 19:12:58 DEBUG lightning::ln::peer_handler: Got Err message from 02b103838b4fc38a423342e2d187de6da76dde13e7a0271c8247e19c91027140f7: Channel closed because of an exception: Funding transaction was un-confirmed. Locked at 1 confs, now have 0 confs. 2023-03-10 19:12:58 ERROR lightning::ln::channelmanager: Force-closing channel dbab3910068bf81fd7c0148cc5ef48686528036493e0a43324d54c7b4839d43b 2023-03-10 19:12:58 DEBUG lightning::ln::channelmanager: Finishing force-closure of channel with 0 HTLCs to fail ``
I tried to sum up holistically what is the advantage and disadvantage of this new flow against the previous version where the clients were in charge of triggering the trade execution after a match. Advantages:
Disadvantages:
Thinking about this made me come to this question: Who is in charge of optimizing the "trades" so that we don't execute everything sequentially - the orderbook or the coordinator? If we assume that the orderbook, eventually, should be smart enough to batch execution together, i.e. the orderbook should ensure that not all orders are handled sequentially if there are multiple matches, this would mean that the coordinator should actually not question the authority of the orderbook. That could mean that the coordinator does not do any optimizations - in such a case we might as well let the client trigger the execution to overcome the disadvantages listed above. |
assume that there is only one open order in the app and load the open order from the db so we can update it.
Yes let's close it. I kept it open so we could finish our discussions, but we have moved on already. |
Checklist:
resolves #243