mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-10 09:11:55 +00:00
Merge pull request #1350 from c9s/feature/grid2/twin-orderbook
FEATURE: [grid2] twin orderbook
This commit is contained in:
commit
7d97f573c5
|
@ -4,6 +4,7 @@ import (
|
|||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
|
@ -111,3 +112,167 @@ func (m TwinOrderMap) String() string {
|
|||
sb.WriteString("================== END OF PIN ORDER MAP ==================\n")
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// TwinOrderBook is to verify grid
|
||||
// For grid trading, there are twin orders between a grid
|
||||
// e.g. 100, 200, 300, 400, 500
|
||||
// BUY 100 and SELL 200 are a twin.
|
||||
// BUY 200 and SELL 300 are a twin.
|
||||
// Because they can't be placed on orderbook at the same time.
|
||||
// We use sell price to be the twin orderbook's key
|
||||
// New the twin orderbook with pins, and it will sort the pins in asc order.
|
||||
// There must be a non nil TwinOrder on the every pin (except the first one).
|
||||
// But the TwinOrder.Exist() may be false. It means there is no twin order on this grid
|
||||
type TwinOrderBook struct {
|
||||
// used to protect orderbook update
|
||||
mu sync.Mutex
|
||||
|
||||
// sort in asc order
|
||||
pins []fixedpoint.Value
|
||||
|
||||
// pin index, use to find the next or last pin in desc order
|
||||
pinIdx map[fixedpoint.Value]int
|
||||
|
||||
// orderbook
|
||||
m map[fixedpoint.Value]*TwinOrder
|
||||
|
||||
// size is the amount on twin orderbook
|
||||
size int
|
||||
}
|
||||
|
||||
func newTwinOrderBook(pins []Pin) *TwinOrderBook {
|
||||
var v []fixedpoint.Value
|
||||
for _, pin := range pins {
|
||||
v = append(v, fixedpoint.Value(pin))
|
||||
}
|
||||
|
||||
// sort it in asc order
|
||||
sort.Slice(v, func(i, j int) bool {
|
||||
return v[j].Compare(v[i]) > 0
|
||||
})
|
||||
|
||||
pinIdx := make(map[fixedpoint.Value]int)
|
||||
m := make(map[fixedpoint.Value]*TwinOrder)
|
||||
for i, pin := range v {
|
||||
m[pin] = &TwinOrder{}
|
||||
pinIdx[pin] = i
|
||||
}
|
||||
|
||||
return &TwinOrderBook{
|
||||
pins: v,
|
||||
pinIdx: pinIdx,
|
||||
m: m,
|
||||
size: 0,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *TwinOrderBook) String() string {
|
||||
var sb strings.Builder
|
||||
|
||||
sb.WriteString("================== TWIN ORDERBOOK ==================\n")
|
||||
for _, pin := range b.pins {
|
||||
twin := b.m[fixedpoint.Value(pin)]
|
||||
twinOrder := twin.GetOrder()
|
||||
sb.WriteString(fmt.Sprintf("-> %8s) %s\n", pin, twinOrder.String()))
|
||||
}
|
||||
sb.WriteString("================== END OF TWINORDERBOOK ==================\n")
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func (b *TwinOrderBook) GetTwinOrderPin(order types.Order) (fixedpoint.Value, error) {
|
||||
idx, exist := b.pinIdx[order.Price]
|
||||
if !exist {
|
||||
return fixedpoint.Zero, fmt.Errorf("the order's (%d) price (%s) is not in pins", order.OrderID, order.Price)
|
||||
}
|
||||
|
||||
if order.Side == types.SideTypeBuy {
|
||||
// we use sell price as twin orderbook's key, so if the order's side is buy.
|
||||
// we need to find its next price on grid.
|
||||
// e.g.
|
||||
// BUY 100 <- twin -> SELL 200
|
||||
// BUY 200 <- twin -> SELL 300
|
||||
// BUY 300 <- twin -> SELL 400
|
||||
// BUY 400 <- twin -> SELL 500
|
||||
// if the order is BUY 100, we need to find its twin order's price to be the twin orderbook's key
|
||||
// so we plus 1 here and use sorted pins to find the next price (200)
|
||||
// there must no BUY 500 in the grid, so we need to make sure the idx should always not over the len(pins)
|
||||
// also, there must no SELL 100 in the grid, so we need to make sure the idx should always not be 0
|
||||
idx++
|
||||
if idx >= len(b.pins) {
|
||||
return fixedpoint.Zero, fmt.Errorf("this order's twin order price is not in pins, %+v", order)
|
||||
}
|
||||
} else if order.Side == types.SideTypeSell {
|
||||
if idx == 0 {
|
||||
return fixedpoint.Zero, fmt.Errorf("this order's twin order price is at zero index, %+v", order)
|
||||
}
|
||||
// do nothing
|
||||
} else {
|
||||
// should not happen
|
||||
return fixedpoint.Zero, fmt.Errorf("the order's (%d) side (%s) is not supported", order.OrderID, order.Side)
|
||||
}
|
||||
|
||||
return b.pins[idx], nil
|
||||
}
|
||||
|
||||
func (b *TwinOrderBook) AddOrder(order types.Order) error {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
pin, err := b.GetTwinOrderPin(order)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// At all the pins, we already create the empty TwinOrder{}
|
||||
// As a result,if the exist is false, it means the pin is not in the twin orderbook.
|
||||
// That's invalid pin, or we have something wrong when new TwinOrderBook
|
||||
twinOrder, exist := b.m[pin]
|
||||
if !exist {
|
||||
// should not happen
|
||||
return fmt.Errorf("no any empty twin order at pins, should not happen, check it")
|
||||
}
|
||||
|
||||
// Exist == false means there is no twin order on this pin
|
||||
if !twinOrder.Exist() {
|
||||
b.size++
|
||||
}
|
||||
if b.size >= len(b.pins) {
|
||||
return fmt.Errorf("the maximum size of twin orderbook is len(pins) - 1, need to check it")
|
||||
}
|
||||
twinOrder.SetOrder(order)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *TwinOrderBook) GetTwinOrder(pin fixedpoint.Value) *TwinOrder {
|
||||
return b.m[pin]
|
||||
}
|
||||
|
||||
func (b *TwinOrderBook) AddTwinOrder(pin fixedpoint.Value, order *TwinOrder) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
b.m[pin] = order
|
||||
}
|
||||
|
||||
// Size is the valid twin order on grid.
|
||||
func (b *TwinOrderBook) Size() int {
|
||||
return b.size
|
||||
}
|
||||
|
||||
// EmptyTwinOrderSize is the amount of grid there is no twin order on it.
|
||||
func (b *TwinOrderBook) EmptyTwinOrderSize() int {
|
||||
// for grid, there is only pins - 1 order on the grid, so we need to minus 1.
|
||||
return len(b.pins) - 1 - b.size
|
||||
}
|
||||
|
||||
func (b *TwinOrderBook) SyncOrderMap() *types.SyncOrderMap {
|
||||
orderMap := types.NewSyncOrderMap()
|
||||
for _, twin := range b.m {
|
||||
if twin.Exist() {
|
||||
orderMap.Add(twin.GetOrder())
|
||||
}
|
||||
}
|
||||
|
||||
return orderMap
|
||||
}
|
||||
|
|
68
pkg/strategy/grid2/twin_order_test.go
Normal file
68
pkg/strategy/grid2/twin_order_test.go
Normal file
|
@ -0,0 +1,68 @@
|
|||
package grid2
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
"github.com/c9s/bbgo/pkg/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestTwinOrderBook(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
pins := []Pin{
|
||||
Pin(fixedpoint.NewFromInt(3)),
|
||||
Pin(fixedpoint.NewFromInt(4)),
|
||||
Pin(fixedpoint.NewFromInt(1)),
|
||||
Pin(fixedpoint.NewFromInt(5)),
|
||||
Pin(fixedpoint.NewFromInt(2)),
|
||||
}
|
||||
|
||||
book := newTwinOrderBook(pins)
|
||||
assert.Equal(0, book.Size())
|
||||
assert.Equal(4, book.EmptyTwinOrderSize())
|
||||
for _, pin := range pins {
|
||||
twinOrder := book.GetTwinOrder(fixedpoint.Value(pin))
|
||||
if !assert.NotNil(twinOrder) {
|
||||
continue
|
||||
}
|
||||
|
||||
assert.False(twinOrder.Exist())
|
||||
}
|
||||
|
||||
orders := []types.Order{
|
||||
{
|
||||
OrderID: 1,
|
||||
SubmitOrder: types.SubmitOrder{
|
||||
Price: fixedpoint.NewFromInt(2),
|
||||
Side: types.SideTypeBuy,
|
||||
},
|
||||
},
|
||||
{
|
||||
OrderID: 2,
|
||||
SubmitOrder: types.SubmitOrder{
|
||||
Price: fixedpoint.NewFromInt(4),
|
||||
Side: types.SideTypeSell,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, order := range orders {
|
||||
assert.NoError(book.AddOrder(order))
|
||||
}
|
||||
assert.Equal(2, book.Size())
|
||||
assert.Equal(2, book.EmptyTwinOrderSize())
|
||||
|
||||
for _, order := range orders {
|
||||
pin, err := book.GetTwinOrderPin(order)
|
||||
if !assert.NoError(err) {
|
||||
continue
|
||||
}
|
||||
twinOrder := book.GetTwinOrder(pin)
|
||||
if !assert.True(twinOrder.Exist()) {
|
||||
continue
|
||||
}
|
||||
|
||||
assert.Equal(order.OrderID, twinOrder.GetOrder().OrderID)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user