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

Commit

Permalink
Test buy-side for volume filter with base cap and exact mode (#626)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
debnil authored Dec 22, 2020
1 parent b8f9a67 commit f909f50
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 17 deletions.
34 changes: 28 additions & 6 deletions plugins/volumeFilter.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,18 +191,26 @@ func volumeFilterFn(dailyOTB *VolumeFilterConfig, dailyTBBAccumulator *VolumeFil
return nil, fmt.Errorf("could not convert price (%s) to float: %s", op.Price, e)
}

offerAmount, e := strconv.ParseFloat(op.Amount, 64)
if e != nil {
return nil, fmt.Errorf("could not convert amount (%s) to float: %s", op.Amount, e)
}

// A "buy" op has amount = sellAmount * sellPrice, and price = 1/sellPrice
// So, we adjust the offer variables by "undoing" those adjustments
// We can then use the same computations as sell orders on buy orders
if dailyOTB.action.IsBuy() {
offerAmount = offerAmount * offerPrice
offerPrice = 1 / offerPrice
}

// capPrice is used when computing amounts to sell or buy
// it's the offer price when capping on quote, and 1.0 when capping on base
capPrice := offerPrice
if lp.baseAssetCapInBaseUnits != nil {
capPrice = 1.0
}

offerAmount, e := strconv.ParseFloat(op.Amount, 64)
if e != nil {
return nil, fmt.Errorf("could not convert amount (%s) to float: %s", op.Amount, e)
}

// extracts from base or quote side, depending on filter
otb, tbb, cap, e := extractAllCaps(dailyOTB, dailyTBBAccumulator, lp)
if e != nil {
Expand All @@ -228,8 +236,22 @@ func volumeFilterFn(dailyOTB *VolumeFilterConfig, dailyTBBAccumulator *VolumeFil
return nil, nil
}

op.Amount = fmt.Sprintf("%.7f", newOfferAmount)
dailyTBBAccumulator = updateTBB(dailyTBBAccumulator, newOfferAmount, offerPrice)

// if we have a buy operation, we want to make sure buy ops have the same relationship between price and amount
// to do this, we apply the same amount adjustment as `makeBuyOpAmtPrice`
// The following conversion is done above on input:
// sellOfferAmount = buyOfferAmount * buyOfferPrice
// sellOfferPrice = 1 / buyOfferPrice
//
// Therefore we need to undo it using the following:
// newOpAmount = newOpAmount * sellOfferPrice
// newOpAmount => newOpAmount * 1 / buyOfferPrice
newOpAmount := newOfferAmount
if dailyOTB.action.IsBuy() {
newOpAmount = newOpAmount * offerPrice
}
op.Amount = fmt.Sprintf("%.7f", newOpAmount)
return op, nil
}

Expand Down
73 changes: 62 additions & 11 deletions plugins/volumeFilter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ import (
"github.com/stretchr/testify/assert"
)

var testBaseAsset = txnbuild.NativeAsset{}
var testQuoteAsset txnbuild.CreditAsset = txnbuild.CreditAsset{Code: "QUOTE", Issuer: "GBGQAGAMK6W6FH6AGGZ2BI2MY5TA5VJEHU2DQRFXACMAZHNRD3SXEV6Z"}

func makeWantVolumeFilter(config *VolumeFilterConfig, marketIDs []string, accountIDs []string, action queries.DailyVolumeAction) *volumeFilter {
query, e := queries.MakeDailyVolumeByDateForMarketIdsAction(&sql.DB{}, marketIDs, action, accountIDs)
if e != nil {
Expand Down Expand Up @@ -409,19 +412,20 @@ func TestVolumeFilterFn_BaseCap_Exact(t *testing.T) {
wantTbbQuote: 0,
},
}

for _, k := range testCases {
// convert to common format accepted by runTestVolumeFilterFn
// doing this explicitly here is easier to read rather than if we were to add "logic" to convert it to a standard format
inputOp := makeSellOpAmtPrice(k.inputAmount, k.inputPrice)

var wantOp *txnbuild.ManageSellOffer
// test sell action
sellName := fmt.Sprintf("%s, sell", k.name)
inputSellOp := makeSellOpAmtPrice(k.inputAmount, k.inputPrice)
var wantSellOp *txnbuild.ManageSellOffer
if k.wantPrice != nil && k.wantAmount != nil {
wantOp = makeSellOpAmtPrice(*k.wantAmount, *k.wantPrice)
wantSellOp = makeSellOpAmtPrice(*k.wantAmount, *k.wantPrice)
}

runTestVolumeFilterFn(
t,
k.name,
sellName,
volumeFilterModeExact,
queries.DailyVolumeActionSell,
pointy.Float64(k.cap), // base cap
Expand All @@ -430,8 +434,33 @@ func TestVolumeFilterFn_BaseCap_Exact(t *testing.T) {
nil, // quoteOTB nil because this test is for the BaseCap
pointy.Float64(k.tbb), // baseTBB
pointy.Float64(0), // quoteTBB (non-nil since it accumulates)
inputOp,
wantOp,
inputSellOp,
wantSellOp,
pointy.Float64(k.wantTbbBase),
pointy.Float64(k.wantTbbQuote),
)

// test buy action
buyName := fmt.Sprintf("%s, buy", k.name)
inputBuyOp := makeBuyOpAmtPrice(k.inputAmount, k.inputPrice)
var wantBuyOp *txnbuild.ManageSellOffer
if k.wantPrice != nil && k.wantAmount != nil {
wantBuyOp = makeBuyOpAmtPrice(*k.wantAmount, *k.wantPrice)
}

runTestVolumeFilterFn(
t,
buyName,
volumeFilterModeExact,
queries.DailyVolumeActionBuy,
pointy.Float64(k.cap), // base cap
nil, // quote cap nil because this test is for the BaseCap
pointy.Float64(k.otb), // baseOTB
nil, // quoteOTB nil because this test is for the BaseCap
pointy.Float64(k.tbb), // baseTBB
pointy.Float64(0), // quoteTBB (non-nil since it accumulates)
inputBuyOp,
wantBuyOp,
pointy.Float64(k.wantTbbBase),
pointy.Float64(k.wantTbbQuote),
)
Expand Down Expand Up @@ -1111,7 +1140,9 @@ func runTestVolumeFilterFn(
mode: mode,
}

actual, e := volumeFilterFn(dailyOTB, dailyTBBAccumulator, inputOp, utils.NativeAsset, utils.NativeAsset, lp)
base := utils.Asset2Asset2(testBaseAsset)
quote := utils.Asset2Asset2(testQuoteAsset)
actual, e := volumeFilterFn(dailyOTB, dailyTBBAccumulator, inputOp, base, quote, lp)
if !assert.Nil(t, e) {
return
}
Expand All @@ -1126,13 +1157,33 @@ func runTestVolumeFilterFn(

func makeSellOpAmtPrice(amount float64, price float64) *txnbuild.ManageSellOffer {
return &txnbuild.ManageSellOffer{
Buying: txnbuild.NativeAsset{},
Selling: txnbuild.NativeAsset{},
Buying: testQuoteAsset,
Selling: testBaseAsset,
Amount: fmt.Sprintf("%.7f", amount),
Price: fmt.Sprintf("%.7f", price),
}
}

func makeBuyOpAmtPrice(amount float64, price float64) *txnbuild.ManageSellOffer {
// in Kelp, all actions are performed in context of the base asset
// a sell op sells base, and a buy op buys base/sells quote
// 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:
// 2020/12/23 02:35:59 submitting the following ops
// 2020/12/23 02:35:59 &{Selling:{Code:COUPON Issuer:GBMMZMK2DC4FFP4CAI6KCVNCQ7WLO5A7DQU7EC7WGHRDQBZB763X4OQI} Buying:{} Amount:163.4735274 Price:6.1171984 OfferID:0 SourceAccount:0xc00047e5e0}
// 2020/12/23 02:35:59 &{Selling:{} Buying:{Code:COUPON Issuer:GBMMZMK2DC4FFP4CAI6KCVNCQ7WLO5A7DQU7EC7WGHRDQBZB763X4OQI} Amount:1000.0000000 Price:0.1638006 OfferID:0 SourceAccount:0xc00047e6a0}
// above, we can see that the selling operation (second op) has amount = 1000.0 and price = 0.16 which is correct
// the buying op (first op) has amount set to 163.xx and price set to 6.xx, and we need to replicate this.
// To do so, we need to:
// set our Amount field on the manageSellOffer here equal to amount * price (so we get 1000 * 0.16 ~= 163.xx like displayed above)
// set the Price field to 1/price (so we get 1/0.16 ~= 6.xx like displayed above)
return &txnbuild.ManageSellOffer{
Buying: testBaseAsset,
Selling: testQuoteAsset,
Amount: fmt.Sprintf("%.7f", amount*price),
Price: fmt.Sprintf("%.7f", 1/price),
}
}

func TestValidateConfig(t *testing.T) {
testCases := []struct {
name string
Expand Down

0 comments on commit f909f50

Please sign in to comment.