Skip to content
This repository was archived by the owner on Feb 1, 2024. It is now read-only.

Commit f909f50

Browse files
authored
Test buy-side for volume filter with base cap and exact mode (#626)
* Rename test to sell * Initial commit * Remove duplicate test cases, loop over actions * Change asset definition * Small changes. * Unroll actions loop * Define base and quote assets * Switch base and quote assets * Modify make buy op * Modify computation for buy op * Add clarifying comment to new op amount
1 parent b8f9a67 commit f909f50

2 files changed

Lines changed: 90 additions & 17 deletions

File tree

plugins/volumeFilter.go

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -191,18 +191,26 @@ func volumeFilterFn(dailyOTB *VolumeFilterConfig, dailyTBBAccumulator *VolumeFil
191191
return nil, fmt.Errorf("could not convert price (%s) to float: %s", op.Price, e)
192192
}
193193

194+
offerAmount, e := strconv.ParseFloat(op.Amount, 64)
195+
if e != nil {
196+
return nil, fmt.Errorf("could not convert amount (%s) to float: %s", op.Amount, e)
197+
}
198+
199+
// A "buy" op has amount = sellAmount * sellPrice, and price = 1/sellPrice
200+
// So, we adjust the offer variables by "undoing" those adjustments
201+
// We can then use the same computations as sell orders on buy orders
202+
if dailyOTB.action.IsBuy() {
203+
offerAmount = offerAmount * offerPrice
204+
offerPrice = 1 / offerPrice
205+
}
206+
194207
// capPrice is used when computing amounts to sell or buy
195208
// it's the offer price when capping on quote, and 1.0 when capping on base
196209
capPrice := offerPrice
197210
if lp.baseAssetCapInBaseUnits != nil {
198211
capPrice = 1.0
199212
}
200213

201-
offerAmount, e := strconv.ParseFloat(op.Amount, 64)
202-
if e != nil {
203-
return nil, fmt.Errorf("could not convert amount (%s) to float: %s", op.Amount, e)
204-
}
205-
206214
// extracts from base or quote side, depending on filter
207215
otb, tbb, cap, e := extractAllCaps(dailyOTB, dailyTBBAccumulator, lp)
208216
if e != nil {
@@ -228,8 +236,22 @@ func volumeFilterFn(dailyOTB *VolumeFilterConfig, dailyTBBAccumulator *VolumeFil
228236
return nil, nil
229237
}
230238

231-
op.Amount = fmt.Sprintf("%.7f", newOfferAmount)
232239
dailyTBBAccumulator = updateTBB(dailyTBBAccumulator, newOfferAmount, offerPrice)
240+
241+
// if we have a buy operation, we want to make sure buy ops have the same relationship between price and amount
242+
// to do this, we apply the same amount adjustment as `makeBuyOpAmtPrice`
243+
// The following conversion is done above on input:
244+
// sellOfferAmount = buyOfferAmount * buyOfferPrice
245+
// sellOfferPrice = 1 / buyOfferPrice
246+
//
247+
// Therefore we need to undo it using the following:
248+
// newOpAmount = newOpAmount * sellOfferPrice
249+
// newOpAmount => newOpAmount * 1 / buyOfferPrice
250+
newOpAmount := newOfferAmount
251+
if dailyOTB.action.IsBuy() {
252+
newOpAmount = newOpAmount * offerPrice
253+
}
254+
op.Amount = fmt.Sprintf("%.7f", newOpAmount)
233255
return op, nil
234256
}
235257

plugins/volumeFilter_test.go

Lines changed: 62 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ import (
1515
"github.com/stretchr/testify/assert"
1616
)
1717

18+
var testBaseAsset = txnbuild.NativeAsset{}
19+
var testQuoteAsset txnbuild.CreditAsset = txnbuild.CreditAsset{Code: "QUOTE", Issuer: "GBGQAGAMK6W6FH6AGGZ2BI2MY5TA5VJEHU2DQRFXACMAZHNRD3SXEV6Z"}
20+
1821
func makeWantVolumeFilter(config *VolumeFilterConfig, marketIDs []string, accountIDs []string, action queries.DailyVolumeAction) *volumeFilter {
1922
query, e := queries.MakeDailyVolumeByDateForMarketIdsAction(&sql.DB{}, marketIDs, action, accountIDs)
2023
if e != nil {
@@ -409,19 +412,20 @@ func TestVolumeFilterFn_BaseCap_Exact(t *testing.T) {
409412
wantTbbQuote: 0,
410413
},
411414
}
415+
412416
for _, k := range testCases {
413417
// convert to common format accepted by runTestVolumeFilterFn
414-
// doing this explicitly here is easier to read rather than if we were to add "logic" to convert it to a standard format
415-
inputOp := makeSellOpAmtPrice(k.inputAmount, k.inputPrice)
416-
417-
var wantOp *txnbuild.ManageSellOffer
418+
// test sell action
419+
sellName := fmt.Sprintf("%s, sell", k.name)
420+
inputSellOp := makeSellOpAmtPrice(k.inputAmount, k.inputPrice)
421+
var wantSellOp *txnbuild.ManageSellOffer
418422
if k.wantPrice != nil && k.wantAmount != nil {
419-
wantOp = makeSellOpAmtPrice(*k.wantAmount, *k.wantPrice)
423+
wantSellOp = makeSellOpAmtPrice(*k.wantAmount, *k.wantPrice)
420424
}
421425

422426
runTestVolumeFilterFn(
423427
t,
424-
k.name,
428+
sellName,
425429
volumeFilterModeExact,
426430
queries.DailyVolumeActionSell,
427431
pointy.Float64(k.cap), // base cap
@@ -430,8 +434,33 @@ func TestVolumeFilterFn_BaseCap_Exact(t *testing.T) {
430434
nil, // quoteOTB nil because this test is for the BaseCap
431435
pointy.Float64(k.tbb), // baseTBB
432436
pointy.Float64(0), // quoteTBB (non-nil since it accumulates)
433-
inputOp,
434-
wantOp,
437+
inputSellOp,
438+
wantSellOp,
439+
pointy.Float64(k.wantTbbBase),
440+
pointy.Float64(k.wantTbbQuote),
441+
)
442+
443+
// test buy action
444+
buyName := fmt.Sprintf("%s, buy", k.name)
445+
inputBuyOp := makeBuyOpAmtPrice(k.inputAmount, k.inputPrice)
446+
var wantBuyOp *txnbuild.ManageSellOffer
447+
if k.wantPrice != nil && k.wantAmount != nil {
448+
wantBuyOp = makeBuyOpAmtPrice(*k.wantAmount, *k.wantPrice)
449+
}
450+
451+
runTestVolumeFilterFn(
452+
t,
453+
buyName,
454+
volumeFilterModeExact,
455+
queries.DailyVolumeActionBuy,
456+
pointy.Float64(k.cap), // base cap
457+
nil, // quote cap nil because this test is for the BaseCap
458+
pointy.Float64(k.otb), // baseOTB
459+
nil, // quoteOTB nil because this test is for the BaseCap
460+
pointy.Float64(k.tbb), // baseTBB
461+
pointy.Float64(0), // quoteTBB (non-nil since it accumulates)
462+
inputBuyOp,
463+
wantBuyOp,
435464
pointy.Float64(k.wantTbbBase),
436465
pointy.Float64(k.wantTbbQuote),
437466
)
@@ -1111,7 +1140,9 @@ func runTestVolumeFilterFn(
11111140
mode: mode,
11121141
}
11131142

1114-
actual, e := volumeFilterFn(dailyOTB, dailyTBBAccumulator, inputOp, utils.NativeAsset, utils.NativeAsset, lp)
1143+
base := utils.Asset2Asset2(testBaseAsset)
1144+
quote := utils.Asset2Asset2(testQuoteAsset)
1145+
actual, e := volumeFilterFn(dailyOTB, dailyTBBAccumulator, inputOp, base, quote, lp)
11151146
if !assert.Nil(t, e) {
11161147
return
11171148
}
@@ -1126,13 +1157,33 @@ func runTestVolumeFilterFn(
11261157

11271158
func makeSellOpAmtPrice(amount float64, price float64) *txnbuild.ManageSellOffer {
11281159
return &txnbuild.ManageSellOffer{
1129-
Buying: txnbuild.NativeAsset{},
1130-
Selling: txnbuild.NativeAsset{},
1160+
Buying: testQuoteAsset,
1161+
Selling: testBaseAsset,
11311162
Amount: fmt.Sprintf("%.7f", amount),
11321163
Price: fmt.Sprintf("%.7f", price),
11331164
}
11341165
}
11351166

1167+
func makeBuyOpAmtPrice(amount float64, price float64) *txnbuild.ManageSellOffer {
1168+
// in Kelp, all actions are performed in context of the base asset
1169+
// a sell op sells base, and a buy op buys base/sells quote
1170+
// I ran this using the buysell strategy with the amount set to 1000.0 units (of base on either side), with the buy op being displayed first:
1171+
// 2020/12/23 02:35:59 submitting the following ops
1172+
// 2020/12/23 02:35:59 &{Selling:{Code:COUPON Issuer:GBMMZMK2DC4FFP4CAI6KCVNCQ7WLO5A7DQU7EC7WGHRDQBZB763X4OQI} Buying:{} Amount:163.4735274 Price:6.1171984 OfferID:0 SourceAccount:0xc00047e5e0}
1173+
// 2020/12/23 02:35:59 &{Selling:{} Buying:{Code:COUPON Issuer:GBMMZMK2DC4FFP4CAI6KCVNCQ7WLO5A7DQU7EC7WGHRDQBZB763X4OQI} Amount:1000.0000000 Price:0.1638006 OfferID:0 SourceAccount:0xc00047e6a0}
1174+
// above, we can see that the selling operation (second op) has amount = 1000.0 and price = 0.16 which is correct
1175+
// the buying op (first op) has amount set to 163.xx and price set to 6.xx, and we need to replicate this.
1176+
// To do so, we need to:
1177+
// set our Amount field on the manageSellOffer here equal to amount * price (so we get 1000 * 0.16 ~= 163.xx like displayed above)
1178+
// set the Price field to 1/price (so we get 1/0.16 ~= 6.xx like displayed above)
1179+
return &txnbuild.ManageSellOffer{
1180+
Buying: testBaseAsset,
1181+
Selling: testQuoteAsset,
1182+
Amount: fmt.Sprintf("%.7f", amount*price),
1183+
Price: fmt.Sprintf("%.7f", 1/price),
1184+
}
1185+
}
1186+
11361187
func TestValidateConfig(t *testing.T) {
11371188
testCases := []struct {
11381189
name string

0 commit comments

Comments
 (0)