mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-21 22:43:52 +00:00
xdepthmaker: add tests to the generateMakerOrders
This commit is contained in:
parent
263c0883d1
commit
46b3a81b07
67
config/xdepthmaker.yaml
Normal file
67
config/xdepthmaker.yaml
Normal file
|
@ -0,0 +1,67 @@
|
|||
---
|
||||
notifications:
|
||||
slack:
|
||||
defaultChannel: "dev-bbgo"
|
||||
errorChannel: "bbgo-error"
|
||||
|
||||
switches:
|
||||
trade: true
|
||||
orderUpdate: false
|
||||
submitOrder: false
|
||||
|
||||
persistence:
|
||||
json:
|
||||
directory: var/data
|
||||
redis:
|
||||
host: 127.0.0.1
|
||||
port: 6379
|
||||
db: 0
|
||||
|
||||
logging:
|
||||
trade: true
|
||||
order: true
|
||||
fields:
|
||||
env: staging
|
||||
|
||||
sessions:
|
||||
max:
|
||||
exchange: max
|
||||
envVarPrefix: max
|
||||
|
||||
binance:
|
||||
exchange: binance
|
||||
envVarPrefix: binance
|
||||
|
||||
crossExchangeStrategies:
|
||||
|
||||
- xdepthmaker:
|
||||
symbol: "BTCUSDT"
|
||||
makerExchange: max
|
||||
hedgeExchange: binance
|
||||
|
||||
# disableHedge disables the hedge orders on the source exchange
|
||||
# disableHedge: true
|
||||
|
||||
hedgeInterval: 10s
|
||||
notifyTrade: true
|
||||
|
||||
margin: 0.004
|
||||
askMargin: 0.4%
|
||||
bidMargin: 0.4%
|
||||
|
||||
depthScale:
|
||||
byLayer:
|
||||
linear:
|
||||
domain: [1, 30]
|
||||
range: [50, 20_000]
|
||||
|
||||
# numLayers means how many order we want to place on each side. 3 means we want 3 bid orders and 3 ask orders
|
||||
numLayers: 30
|
||||
|
||||
# pips is the fraction numbers between each order. for BTC, 1 pip is 0.1,
|
||||
# 0.1 pip is 0.01, here we use 10, so we will get 18000.00, 18001.00 and
|
||||
# 18002.00
|
||||
pips: 10
|
||||
persistence:
|
||||
type: redis
|
||||
|
|
@ -202,6 +202,9 @@ type Strategy struct {
|
|||
// QuantityScale helps user to define the quantity by layer scale
|
||||
QuantityScale *bbgo.LayerScale `json:"quantityScale,omitempty"`
|
||||
|
||||
// DepthScale helps user to define the depth by layer scale
|
||||
DepthScale *bbgo.LayerScale `json:"depthScale,omitempty"`
|
||||
|
||||
// MaxExposurePosition defines the unhedged quantity of stop
|
||||
MaxExposurePosition fixedpoint.Value `json:"maxExposurePosition"`
|
||||
|
||||
|
@ -266,8 +269,8 @@ func (s *Strategy) Validate() error {
|
|||
return errors.New("maker exchange is not configured")
|
||||
}
|
||||
|
||||
if s.Quantity.IsZero() || s.QuantityScale == nil {
|
||||
return errors.New("quantity or quantityScale can not be empty")
|
||||
if s.DepthScale == nil {
|
||||
return errors.New("depthScale can not be empty")
|
||||
}
|
||||
|
||||
if len(s.Symbol) == 0 {
|
||||
|
@ -576,7 +579,106 @@ func (s *Strategy) runTradeRecover(ctx context.Context) {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *Strategy) updateQuote(ctx context.Context, orderExecutionRouter bbgo.OrderExecutionRouter) {
|
||||
func (s *Strategy) generateMakerOrders(pricingBook *types.StreamOrderBook) ([]types.SubmitOrder, error) {
|
||||
bestBid, bestAsk, hasPrice := pricingBook.BestBidAndAsk()
|
||||
if !hasPrice {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
bestBidPrice := bestBid.Price
|
||||
bestAskPrice := bestAsk.Price
|
||||
log.Infof("%s book ticker: best ask / best bid = %v / %v", s.Symbol, bestAskPrice, bestBidPrice)
|
||||
|
||||
lastMidPrice := bestBidPrice.Add(bestAskPrice).Div(Two)
|
||||
_ = lastMidPrice
|
||||
|
||||
var submitOrders []types.SubmitOrder
|
||||
var accumulatedBidQuantity = fixedpoint.Zero
|
||||
var accumulatedAskQuantity = fixedpoint.Zero
|
||||
var accumulatedBidQuoteQuantity = fixedpoint.Zero
|
||||
|
||||
dupPricingBook := pricingBook.CopyDepth(0)
|
||||
|
||||
for _, side := range []types.SideType{types.SideTypeBuy, types.SideTypeSell} {
|
||||
for i := 1; i <= s.NumLayers; i++ {
|
||||
requiredDepthFloat, err := s.DepthScale.Scale(i)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "depthScale scale error")
|
||||
}
|
||||
|
||||
// requiredDepth is the required depth in quote currency
|
||||
requiredDepth := fixedpoint.NewFromFloat(requiredDepthFloat)
|
||||
|
||||
sideBook := dupPricingBook.SideBook(side)
|
||||
index := sideBook.IndexByQuoteVolumeDepth(requiredDepth)
|
||||
|
||||
pvs := types.PriceVolumeSlice{}
|
||||
if index == -1 {
|
||||
pvs = sideBook[:]
|
||||
} else {
|
||||
pvs = sideBook[0 : index+1]
|
||||
}
|
||||
|
||||
log.Infof("required depth: %f, pvs: %+v", requiredDepth.Float64(), pvs)
|
||||
|
||||
depthPrice, err := averageDepthPrice(pvs)
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("error aggregating depth price")
|
||||
continue
|
||||
}
|
||||
|
||||
switch side {
|
||||
case types.SideTypeBuy:
|
||||
if s.BidMargin.Sign() > 0 {
|
||||
depthPrice = depthPrice.Mul(fixedpoint.One.Sub(s.BidMargin))
|
||||
}
|
||||
|
||||
depthPrice = depthPrice.Round(s.makerMarket.PricePrecision+1, fixedpoint.Down)
|
||||
|
||||
case types.SideTypeSell:
|
||||
if s.AskMargin.Sign() > 0 {
|
||||
depthPrice = depthPrice.Mul(fixedpoint.One.Add(s.AskMargin))
|
||||
}
|
||||
|
||||
depthPrice = depthPrice.Round(s.makerMarket.PricePrecision+1, fixedpoint.Up)
|
||||
}
|
||||
|
||||
depthPrice = s.makerMarket.TruncatePrice(depthPrice)
|
||||
|
||||
quantity := requiredDepth.Div(depthPrice)
|
||||
quantity = s.makerMarket.TruncateQuantity(quantity)
|
||||
log.Infof("side: %s required depth: %f price: %f quantity: %f", side, requiredDepth.Float64(), depthPrice.Float64(), quantity.Float64())
|
||||
|
||||
switch side {
|
||||
case types.SideTypeBuy:
|
||||
quantity = quantity.Sub(accumulatedBidQuantity)
|
||||
|
||||
accumulatedBidQuantity = accumulatedBidQuantity.Add(quantity)
|
||||
quoteQuantity := fixedpoint.Mul(quantity, depthPrice)
|
||||
quoteQuantity = quoteQuantity.Round(s.makerMarket.PricePrecision, fixedpoint.Up)
|
||||
accumulatedBidQuoteQuantity = accumulatedBidQuoteQuantity.Add(quoteQuantity)
|
||||
|
||||
case types.SideTypeSell:
|
||||
quantity = quantity.Sub(accumulatedAskQuantity)
|
||||
accumulatedAskQuantity = accumulatedAskQuantity.Add(quantity)
|
||||
|
||||
}
|
||||
|
||||
submitOrders = append(submitOrders, types.SubmitOrder{
|
||||
Symbol: s.Symbol,
|
||||
Type: types.OrderTypeLimitMaker,
|
||||
Market: s.makerMarket,
|
||||
Side: side,
|
||||
Price: depthPrice,
|
||||
Quantity: quantity,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return submitOrders, nil
|
||||
}
|
||||
|
||||
func (s *Strategy) updateQuote(ctx context.Context) {
|
||||
if err := s.MakerOrderExecutor.GracefulCancel(ctx); err != nil {
|
||||
log.Warnf("there are some %s orders not canceled, skipping placing maker orders", s.Symbol)
|
||||
s.MakerOrderExecutor.ActiveMakerOrders().Print()
|
||||
|
@ -844,3 +946,22 @@ func selectSessions2(
|
|||
s2 = sessions[n2]
|
||||
return s1, s2, nil
|
||||
}
|
||||
|
||||
func averageDepthPrice(pvs types.PriceVolumeSlice) (price fixedpoint.Value, err error) {
|
||||
if len(pvs) == 0 {
|
||||
return fixedpoint.Zero, fmt.Errorf("empty pv slice")
|
||||
}
|
||||
|
||||
totalQuoteAmount := fixedpoint.Zero
|
||||
totalQuantity := fixedpoint.Zero
|
||||
|
||||
for i := 0; i < len(pvs); i++ {
|
||||
pv := pvs[i]
|
||||
quoteAmount := fixedpoint.Mul(pv.Volume, pv.Price)
|
||||
totalQuoteAmount = totalQuoteAmount.Add(quoteAmount)
|
||||
totalQuantity = totalQuantity.Add(pv.Volume)
|
||||
}
|
||||
|
||||
price = totalQuoteAmount.Div(totalQuantity)
|
||||
return price, nil
|
||||
}
|
||||
|
|
72
pkg/strategy/xdepthmaker/strategy_test.go
Normal file
72
pkg/strategy/xdepthmaker/strategy_test.go
Normal file
|
@ -0,0 +1,72 @@
|
|||
package xdepthmaker
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/bbgo"
|
||||
. "github.com/c9s/bbgo/pkg/testing/testhelper"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
)
|
||||
|
||||
func newTestBTCUSDTMarket() types.Market {
|
||||
return types.Market{
|
||||
BaseCurrency: "BTC",
|
||||
QuoteCurrency: "USDT",
|
||||
TickSize: Number(0.01),
|
||||
StepSize: Number(0.000001),
|
||||
PricePrecision: 2,
|
||||
VolumePrecision: 8,
|
||||
MinNotional: Number(8.0),
|
||||
MinQuantity: Number(0.0003),
|
||||
}
|
||||
}
|
||||
|
||||
func TestStrategy_generateMakerOrders(t *testing.T) {
|
||||
s := &Strategy{
|
||||
Symbol: "BTCUSDT",
|
||||
NumLayers: 3,
|
||||
DepthScale: &bbgo.LayerScale{
|
||||
LayerRule: &bbgo.SlideRule{
|
||||
LinearScale: &bbgo.LinearScale{
|
||||
Domain: [2]float64{1.0, 3.0},
|
||||
Range: [2]float64{1000.0, 15000.0},
|
||||
},
|
||||
},
|
||||
},
|
||||
CrossExchangeMarketMakingStrategy: &CrossExchangeMarketMakingStrategy{
|
||||
makerMarket: newTestBTCUSDTMarket(),
|
||||
},
|
||||
}
|
||||
|
||||
pricingBook := types.NewStreamBook("BTCUSDT")
|
||||
pricingBook.OrderBook.Load(types.SliceOrderBook{
|
||||
Symbol: "BTCUSDT",
|
||||
Bids: types.PriceVolumeSlice{
|
||||
{Price: Number("25000.00"), Volume: Number("0.1")},
|
||||
{Price: Number("24900.00"), Volume: Number("0.2")},
|
||||
{Price: Number("24800.00"), Volume: Number("0.3")},
|
||||
{Price: Number("24700.00"), Volume: Number("0.4")},
|
||||
},
|
||||
Asks: types.PriceVolumeSlice{
|
||||
{Price: Number("25100.00"), Volume: Number("0.1")},
|
||||
{Price: Number("25200.00"), Volume: Number("0.2")},
|
||||
{Price: Number("25300.00"), Volume: Number("0.3")},
|
||||
{Price: Number("25400.00"), Volume: Number("0.4")},
|
||||
},
|
||||
Time: time.Now(),
|
||||
})
|
||||
|
||||
orders, err := s.generateMakerOrders(pricingBook)
|
||||
assert.NoError(t, err)
|
||||
AssertOrdersPriceSideQuantity(t, []PriceSideQuantityAssert{
|
||||
{Side: types.SideTypeBuy, Price: Number("25000"), Quantity: Number("0.04")}, // =~ $1000.00
|
||||
{Side: types.SideTypeBuy, Price: Number("24866.66"), Quantity: Number("0.281715")}, // =~ $7005.3111219, accumulated amount =~ $1000.00 + $7005.3111219 = $8005.3111219
|
||||
{Side: types.SideTypeBuy, Price: Number("24800"), Quantity: Number("0.283123")}, // =~ $7021.4504, accumulated amount =~ $1000.00 + $7005.3111219 + $7021.4504 = $8005.3111219 + $7021.4504 =~ $15026.7615219
|
||||
{Side: types.SideTypeSell, Price: Number("25100"), Quantity: Number("0.03984")},
|
||||
{Side: types.SideTypeSell, Price: Number("25233.33"), Quantity: Number("0.2772")},
|
||||
{Side: types.SideTypeSell, Price: Number("25233.33"), Quantity: Number("0.277411")},
|
||||
}, orders)
|
||||
}
|
|
@ -32,7 +32,7 @@ type PriceSideQuantityAssert struct {
|
|||
func AssertOrdersPriceSideQuantity(
|
||||
t *testing.T, asserts []PriceSideQuantityAssert, orders []types.SubmitOrder,
|
||||
) {
|
||||
assert.Equalf(t, len(orders), len(asserts), "expecting %d orders", len(asserts))
|
||||
assert.Equalf(t, len(asserts), len(orders), "expecting %d orders", len(asserts))
|
||||
|
||||
var assertPrices, orderPrices fixedpoint.Slice
|
||||
var assertPricesFloat, orderPricesFloat []float64
|
||||
|
|
|
@ -38,7 +38,7 @@ func (slice PriceVolumeSlice) Trim() (pvs PriceVolumeSlice) {
|
|||
}
|
||||
|
||||
func (slice PriceVolumeSlice) CopyDepth(depth int) PriceVolumeSlice {
|
||||
if depth > len(slice) {
|
||||
if depth == 0 || depth > len(slice) {
|
||||
return slice.Copy()
|
||||
}
|
||||
|
||||
|
@ -67,8 +67,23 @@ func (slice PriceVolumeSlice) First() (PriceVolume, bool) {
|
|||
return PriceVolume{}, false
|
||||
}
|
||||
|
||||
func (slice PriceVolumeSlice) IndexByQuoteVolumeDepth(requiredQuoteVolume fixedpoint.Value) int {
|
||||
var totalQuoteVolume = fixedpoint.Zero
|
||||
for x, pv := range slice {
|
||||
// this should use float64 multiply
|
||||
quoteVolume := fixedpoint.Mul(pv.Volume, pv.Price)
|
||||
totalQuoteVolume = totalQuoteVolume.Add(quoteVolume)
|
||||
if totalQuoteVolume.Compare(requiredQuoteVolume) >= 0 {
|
||||
return x
|
||||
}
|
||||
}
|
||||
|
||||
// depth not enough
|
||||
return -1
|
||||
}
|
||||
|
||||
func (slice PriceVolumeSlice) IndexByVolumeDepth(requiredVolume fixedpoint.Value) int {
|
||||
var tv fixedpoint.Value = fixedpoint.Zero
|
||||
var tv = fixedpoint.Zero
|
||||
for x, el := range slice {
|
||||
tv = tv.Add(el.Volume)
|
||||
if tv.Compare(requiredVolume) >= 0 {
|
||||
|
@ -76,7 +91,7 @@ func (slice PriceVolumeSlice) IndexByVolumeDepth(requiredVolume fixedpoint.Value
|
|||
}
|
||||
}
|
||||
|
||||
// not deep enough
|
||||
// depth not enough
|
||||
return -1
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user