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

Commit

Permalink
mirror max base volume cap, closes #556 (#557)
Browse files Browse the repository at this point in the history
* 1 - mirror strategy config: MAX_ORDER_BASE_CAP

* 2 - usage of new config field + tests

* 3 - fix bug in max base volume cap + test
  • Loading branch information
nikhilsaraf authored Oct 25, 2020
1 parent b4ea3fe commit 797afbf
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 31 deletions.
4 changes: 4 additions & 0 deletions examples/configs/trader/sample_mirror.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ BID_VOLUME_DIVIDE_BY=4.0
# use -1.0 if you want an empty side for the asks
ASK_VOLUME_DIVIDE_BY=5.0

# uncomment this to set a cap on the size of the order in base units. If the backing order after dividing is larger then the bot will cap it to this amount.
# this config param helps you control your risk so you do not place large orders if the backing exchange has one big order.
#MAX_ORDER_BASE_CAP=10000.0

# spread % we should maintain per level between the mirrored exchange and SDEX (0 < spread < 1.0). This moves the price away from the center price on SDEX so we can cover the position on the external exchange, i.e. if this value is > 0 then the spread you provide on SDEX will be more than the spread on the exchange you are mirroring.
# in this example the spread is 0.5%
PER_LEVEL_SPREAD=0.005
Expand Down
17 changes: 14 additions & 3 deletions plugins/mirrorStrategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type mirrorConfig struct {
VolumeDivideByDeprecated *float64 `valid:"-" toml:"VOLUME_DIVIDE_BY" deprecated:"true"`
BidVolumeDivideBy *float64 `valid:"-" toml:"BID_VOLUME_DIVIDE_BY"`
AskVolumeDivideBy *float64 `valid:"-" toml:"ASK_VOLUME_DIVIDE_BY"`
MaxOrderBaseCap float64 `valid:"-" toml:"MAX_ORDER_BASE_CAP"`
PerLevelSpread float64 `valid:"-" toml:"PER_LEVEL_SPREAD"`
PricePrecisionOverride *int8 `valid:"-" toml:"PRICE_PRECISION_OVERRIDE"`
VolumePrecisionOverride *int8 `valid:"-" toml:"VOLUME_PRECISION_OVERRIDE"`
Expand Down Expand Up @@ -89,6 +90,7 @@ type mirrorStrategy struct {
perLevelSpread float64
bidVolumeDivideBy float64
askVolumeDivideBy float64
maxOrderBaseCap float64
exchange api.Exchange
offsetTrades bool
mutex *sync.Mutex
Expand Down Expand Up @@ -168,6 +170,11 @@ func makeMirrorStrategy(
return nil, fmt.Errorf("invalid mirror strategy config file, ASK_VOLUME_DIVIDE_BY needs to be -1.0 or > 0")
}

if config.MaxOrderBaseCap < 0.0 {
utils.PrintErrorHintf("need to set a valid value for MAX_ORDER_BASE_CAP, needs to be >= 0.0")
return nil, fmt.Errorf("invalid mirror strategy config file, MAX_ORDER_BASE_CAP needs to be >= 0.0")
}

var exchange api.Exchange
var e error
var strategyMirrorTradeTriggerExistsQuery *queries.StrategyMirrorTradeTriggerExists
Expand Down Expand Up @@ -313,6 +320,7 @@ func makeMirrorStrategy(
perLevelSpread: config.PerLevelSpread,
bidVolumeDivideBy: bidVolumeDivideBy,
askVolumeDivideBy: askVolumeDivideBy,
maxOrderBaseCap: config.MaxOrderBaseCap,
exchange: exchange,
offsetTrades: config.OffsetTrades,
mutex: &sync.Mutex{},
Expand Down Expand Up @@ -414,12 +422,12 @@ func (s *mirrorStrategy) UpdateWithOps(
if s.bidVolumeDivideBy == -1.0 {
bids = []model.Order{}
} else {
transformOrders(bids, (1 - s.perLevelSpread), (1.0 / s.bidVolumeDivideBy))
transformOrders(bids, (1 - s.perLevelSpread), (1.0 / s.bidVolumeDivideBy), s.maxOrderBaseCap)
}
if s.askVolumeDivideBy == -1.0 {
asks = []model.Order{}
} else {
transformOrders(asks, (1 + s.perLevelSpread), (1.0 / s.askVolumeDivideBy))
transformOrders(asks, (1 + s.perLevelSpread), (1.0 / s.askVolumeDivideBy), s.maxOrderBaseCap)
}
log.Printf("new orders (orderbook after transformations):\n")
printBidsAndAsks(bids, asks)
Expand Down Expand Up @@ -482,10 +490,13 @@ func (s *mirrorStrategy) UpdateWithOps(
return api.ConvertOperation2TM(ops), nil
}

func transformOrders(orders []model.Order, priceMultiplier float64, volumeMultiplier float64) {
func transformOrders(orders []model.Order, priceMultiplier float64, volumeMultiplier float64, maxVolumeCap float64) {
for _, o := range orders {
*o.Price = *o.Price.Scale(priceMultiplier)
*o.Volume = *o.Volume.Scale(volumeMultiplier)
if maxVolumeCap > 0.0 && o.Volume.AsFloat() > maxVolumeCap {
*o.Volume = *model.NumberFromFloat(maxVolumeCap, o.Volume.Precision())
}
}
}

Expand Down
115 changes: 87 additions & 28 deletions plugins/mirrorStrategy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,39 +9,98 @@ import (
)

func TestTransformOrders(t *testing.T) {
// setup
orders := []model.Order{
testCases := []struct {
name string
inputPrice *model.Number
inputVolume *model.Number
orderAction model.OrderAction
priceMultiplier float64
volumeMultiplier float64
maxVolumeBaseCap float64
wantPrice *model.Number
wantVolume *model.Number
}{
{
Pair: &model.TradingPair{Base: model.XLM, Quote: model.USDT},
OrderAction: model.OrderActionBuy,
OrderType: model.OrderTypeLimit,
Price: model.NumberFromFloat(0.15, 6),
Volume: model.NumberFromFloat(51.5, 5),
}, {
Pair: &model.TradingPair{Base: model.XLM, Quote: model.USDT},
OrderAction: model.OrderActionSell,
OrderType: model.OrderTypeLimit,
Price: model.NumberFromFloat(1.15, 6),
Volume: model.NumberFromFloat(1.5123, 5),
name: "buy below capped",
inputPrice: model.NumberFromFloat(0.15, 6),
inputVolume: model.NumberFromFloat(51.5, 5),
orderAction: model.OrderActionBuy,
priceMultiplier: 0.90,
volumeMultiplier: 0.25,
maxVolumeBaseCap: 15.0,
wantPrice: model.NumberFromFloat(0.135, 6),
wantVolume: model.NumberFromFloat(12.875, 5),
}, {
name: "sell below capped",
inputPrice: model.NumberFromFloat(1.15, 6),
inputVolume: model.NumberFromFloat(1.5123, 5),
orderAction: model.OrderActionSell,
priceMultiplier: 0.90,
volumeMultiplier: 0.25,
maxVolumeBaseCap: 15.0,
wantPrice: model.NumberFromFloat(1.035, 6),
wantVolume: model.NumberFromFloat(0.37808, 5), // round up
}, {
name: "buy above capped",
inputPrice: model.NumberFromFloat(0.15, 6),
inputVolume: model.NumberFromFloat(80.0, 5),
orderAction: model.OrderActionBuy,
priceMultiplier: 0.90,
volumeMultiplier: 0.25,
maxVolumeBaseCap: 15.0,
wantPrice: model.NumberFromFloat(0.135, 6),
wantVolume: model.NumberFromFloat(15.0, 5),
}, {
name: "sell above capped",
inputPrice: model.NumberFromFloat(1.15, 6),
inputVolume: model.NumberFromFloat(151.23, 5),
orderAction: model.OrderActionSell,
priceMultiplier: 0.90,
volumeMultiplier: 0.25,
maxVolumeBaseCap: 15.0,
wantPrice: model.NumberFromFloat(1.035, 6),
wantVolume: model.NumberFromFloat(15.0, 5),
}, {
name: "buy with 0 cap",
inputPrice: model.NumberFromFloat(0.15, 6),
inputVolume: model.NumberFromFloat(80.0, 5),
orderAction: model.OrderActionBuy,
priceMultiplier: 0.90,
volumeMultiplier: 0.25,
maxVolumeBaseCap: 0.0,
wantPrice: model.NumberFromFloat(0.135, 6),
wantVolume: model.NumberFromFloat(20.0, 5),
}, {
name: "sell with 0 cap",
inputPrice: model.NumberFromFloat(1.15, 6),
inputVolume: model.NumberFromFloat(151.23, 5),
orderAction: model.OrderActionSell,
priceMultiplier: 0.90,
volumeMultiplier: 0.25,
maxVolumeBaseCap: 0.0,
wantPrice: model.NumberFromFloat(1.035, 6),
wantVolume: model.NumberFromFloat(37.8075, 5),
},
}

// run
transformOrders(orders, 0.90, 0.25)
for _, k := range testCases {
t.Run(k.name, func(t *testing.T) {
order := model.Order{
Pair: &model.TradingPair{Base: model.XLM, Quote: model.USDT},
OrderAction: k.orderAction,
OrderType: model.OrderTypeLimit,
Price: k.inputPrice,
Volume: k.inputVolume,
}
transformOrders([]model.Order{order}, k.priceMultiplier, k.volumeMultiplier, k.maxVolumeBaseCap)

// validate
order := orders[0]
assert.Equal(t, &model.TradingPair{Base: model.XLM, Quote: model.USDT}, order.Pair)
assert.Equal(t, model.OrderActionBuy, order.OrderAction)
assert.Equal(t, model.OrderTypeLimit, order.OrderType)
assert.Equal(t, model.NumberFromFloat(0.135, 6), order.Price)
assert.Equal(t, model.NumberFromFloat(12.875, 5), order.Volume)
order = orders[1]
assert.Equal(t, &model.TradingPair{Base: model.XLM, Quote: model.USDT}, order.Pair)
assert.Equal(t, model.OrderActionSell, order.OrderAction)
assert.Equal(t, model.OrderTypeLimit, order.OrderType)
assert.Equal(t, model.NumberFromFloat(1.035, 6), order.Price)
assert.Equal(t, model.NumberFromFloat(0.37808, 5), order.Volume) // round up
assert.Equal(t, &model.TradingPair{Base: model.XLM, Quote: model.USDT}, order.Pair)
assert.Equal(t, k.orderAction, order.OrderAction)
assert.Equal(t, model.OrderTypeLimit, order.OrderType)
assert.Equal(t, k.wantPrice, order.Price)
assert.Equal(t, k.wantVolume, order.Volume)
})
}
}

func TestBalanceCoordinatorCheckBalance(t *testing.T) {
Expand Down

0 comments on commit 797afbf

Please sign in to comment.