Skip to content

Commit 2a2dadc

Browse files
committed
xmaker: sync order status after sending hedge order from the executor
1 parent 9c19e7a commit 2a2dadc

File tree

4 files changed

+99
-1
lines changed

4 files changed

+99
-1
lines changed

pkg/core/orderstore.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,9 @@ func (s *OrderStore) Add(orders ...types.Order) {
111111
for _, o := range orders {
112112
if o.OrderID == 0 {
113113
logrus.WithFields(o.LogFields()).Errorf("[orderstore] adding order %+v with OrderID 0", o)
114+
continue
114115
}
116+
115117
old, ok := s.orders[o.OrderID]
116118
if ok && o.Tag == "" && old.Tag != "" {
117119
o.Tag = old.Tag
@@ -126,6 +128,7 @@ func (s *OrderStore) Remove(o types.Order) {
126128

127129
if o.OrderID == 0 {
128130
logrus.WithFields(o.LogFields()).Errorf("[orderstore-Remove] given order %+v with OrderID 0", o)
131+
return
129132
}
130133

131134
delete(s.orders, o.OrderID)

pkg/core/tradecollector.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,6 @@ func (c *TradeCollector) processTrade(trade types.Trade) bool {
302302

303303
c.mu.Lock()
304304

305-
// if it's already done, remove the trade from the trade store
306305
if _, done := c.doneTrades[key]; done {
307306
c.mu.Unlock()
308307
return false

pkg/strategy/xmaker/hedgeexecutor.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,44 @@ func (m *MarketOrderHedgeExecutor) Hedge(
122122
}
123123

124124
m.logger.Infof("hedge order created: %+v", hedgeOrder)
125+
126+
if hedgeOrder != nil {
127+
updatedOrder, err := m.syncOrder(ctx, *hedgeOrder)
128+
if err != nil {
129+
m.logger.WithError(err).WithFields(hedgeOrder.LogFields()).Errorf("failed to sync order: %+v", hedgeOrder)
130+
} else if updatedOrder != nil {
131+
orderLogger := m.logger.WithFields(updatedOrder.LogFields())
132+
// Compare the order quantity, if the order quantity is changed from the server side
133+
// we should uncover the difference
134+
if !updatedOrder.Quantity.Eq(hedgeOrder.Quantity) {
135+
// if we sent sell 1 BTC (-1 BTC), but the order was adjusted to sell 0.1 BTC (-0.1 BTC)
136+
// we should uncover +0.9 BTC to the position exposure (meaning -0.9 BTC the the covered position)
137+
138+
// Net Position: 1.0, Pending: 0.0
139+
// Submit Order { Quantity: 1, Side: Sell }
140+
// Net Position: 1.0, Pending: 1.0
141+
// Order Updated { Quantity: 0.1, Side: Sell }
142+
// to quantityToDelta = -(-0.9) (Sell) = +0.9
143+
// to coverDeltaDiff = +0.9
144+
// to uncover = 0.9 = cover -0.9
145+
// Net Position: 1.0, Pending: 0.1
146+
// Covered Position: 0.1
147+
// Uncover: 0.9
148+
quantityDiff := updatedOrder.Quantity.Sub(hedgeOrder.Quantity)
149+
150+
orderLogger.Infof("hedge order quantity changed from %s to %s, adjusting covered position by %s",
151+
hedgeOrder.Quantity.String(), updatedOrder.Quantity.String(), quantityDiff.String())
152+
153+
if !quantityDiff.IsZero() {
154+
coverDeltaDiff := quantityToDelta(quantityDiff, side)
155+
m.positionExposure.Uncover(coverDeltaDiff)
156+
orderLogger.Infof("hedge order quantity changed from %s to %s, adjusting covered position by %s",
157+
hedgeOrder.Quantity.String(), updatedOrder.Quantity.String(), coverDeltaDiff.String())
158+
}
159+
}
160+
}
161+
}
162+
125163
return nil
126164
}
127165

pkg/strategy/xmaker/hedgemarket.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313

1414
"github.com/c9s/bbgo/pkg/bbgo"
1515
"github.com/c9s/bbgo/pkg/core"
16+
"github.com/c9s/bbgo/pkg/exchange/retry"
1617
"github.com/c9s/bbgo/pkg/fixedpoint"
1718
"github.com/c9s/bbgo/pkg/service"
1819
"github.com/c9s/bbgo/pkg/strategy/xmaker/pricer"
@@ -133,6 +134,8 @@ type HedgeMarket struct {
133134
market types.Market
134135
stream types.Stream
135136

137+
orderQueryService types.ExchangeOrderQueryService
138+
136139
connectivity *types.Connectivity
137140

138141
book *types.StreamOrderBook
@@ -210,6 +213,8 @@ func NewHedgeMarket(
210213
}
211214
logger := log.WithFields(logFields)
212215

216+
orderQueryService, _ := session.Exchange.(types.ExchangeOrderQueryService)
217+
213218
m := &HedgeMarket{
214219
HedgeMarketConfig: config,
215220
session: session,
@@ -218,6 +223,8 @@ func NewHedgeMarket(
218223
book: book,
219224
depthBook: depthBook,
220225

226+
orderQueryService: orderQueryService,
227+
221228
connectivity: connectivity,
222229

223230
positionExposure: NewPositionExposure(symbol),
@@ -268,6 +275,46 @@ func (m *HedgeMarket) SetLogger(logger logrus.FieldLogger) {
268275
})
269276
}
270277

278+
func (m *HedgeMarket) syncOrder(ctx context.Context, order types.Order) (*types.Order, error) {
279+
updatedOrder, err := retry.QueryOrderUntilSuccessful(ctx, m.orderQueryService, order.AsQuery())
280+
if err != nil {
281+
return nil, fmt.Errorf("unable to query order #%d: %w", order.OrderID, err)
282+
} else if updatedOrder != nil {
283+
if updatedOrder.Quantity.Compare(order.Quantity) != 0 {
284+
m.logger.WithFields(order.LogFields()).
285+
Warnf("order quantity changed from %s to %s for order #%d",
286+
order.Quantity.String(), updatedOrder.Quantity.String(), order.OrderID)
287+
}
288+
}
289+
290+
orderTrades, err := retry.QueryOrderTradesUntilSuccessful(ctx, m.orderQueryService, order.AsQuery())
291+
if err != nil {
292+
return updatedOrder, fmt.Errorf("unable to query order #%d trades: %w", order.OrderID, err)
293+
}
294+
295+
for _, trade := range orderTrades {
296+
m.tradeCollector.ProcessTrade(trade)
297+
}
298+
299+
return updatedOrder, nil
300+
}
301+
302+
func (m *HedgeMarket) syncHistoryOrder(ctx context.Context) {
303+
historyOrders := m.orderStore.Orders()
304+
m.logger.Infof("loaded %d historical orders", len(historyOrders))
305+
306+
for _, order := range historyOrders {
307+
updatedOrder, err := m.syncOrder(ctx, order)
308+
if err != nil {
309+
m.logger.WithError(err).WithFields(order.LogFields()).Warnf("failed to sync order")
310+
} else if updatedOrder != nil {
311+
m.orderStore.Update(*updatedOrder)
312+
}
313+
}
314+
315+
m.orderStore.Prune(8 * time.Hour)
316+
}
317+
271318
// SetPriceFeeMode sets the fee mode used when computing hedge quote prices.
272319
// This method is safe to call at runtime.
273320
func (m *HedgeMarket) SetPriceFeeMode(mode FeeMode) {
@@ -646,6 +693,17 @@ func (m *HedgeMarket) calculateDebtQuota(totalValue, debtValue, minMarginLevel,
646693
return debtQuota
647694
}
648695

696+
// Send sends the position delta to the hedge market.
697+
func (m *HedgeMarket) Send(delta fixedpoint.Value) {
698+
select {
699+
case m.positionDeltaC <- delta:
700+
case <-time.After(30 * time.Second):
701+
m.logger.Warnf("position delta channel is full, dropping delta: %f", delta.Float64())
702+
}
703+
}
704+
705+
// hedge executes the hedge logic to adjust the position exposure to zero.
706+
// you should pass the position delta to the positionDeltaC channel
649707
func (m *HedgeMarket) hedge(ctx context.Context) error {
650708
if err := m.hedgeExecutor.Clear(ctx); err != nil {
651709
return fmt.Errorf("failed to clear hedge executor: %w", err)

0 commit comments

Comments
 (0)