mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-10 09:11:55 +00:00
Merge pull request #772 from c9s/fix/backtest-stop-order
backtest: fix stop order backtest, add more test cases and assertions
This commit is contained in:
commit
74d74a2d0f
|
@ -311,8 +311,8 @@ func (m *SimplePriceMatching) BuyToPrice(price fixedpoint.Value) (closedOrders [
|
|||
switch o.Type {
|
||||
|
||||
case types.OrderTypeStopMarket:
|
||||
// should we trigger the order
|
||||
if o.StopPrice.Compare(price) <= 0 {
|
||||
// the price is still lower than the stop price, we will put the order back to the list
|
||||
if price.Compare(o.StopPrice) < 0 {
|
||||
// not triggering it, put it back
|
||||
bidOrders = append(bidOrders, o)
|
||||
break
|
||||
|
@ -325,8 +325,8 @@ func (m *SimplePriceMatching) BuyToPrice(price fixedpoint.Value) (closedOrders [
|
|||
closedOrders = append(closedOrders, o)
|
||||
|
||||
case types.OrderTypeStopLimit:
|
||||
// should we trigger the order?
|
||||
if price.Compare(o.StopPrice) <= 0 {
|
||||
// the price is still lower than the stop price, we will put the order back to the list
|
||||
if price.Compare(o.StopPrice) < 0 {
|
||||
bidOrders = append(bidOrders, o)
|
||||
break
|
||||
}
|
||||
|
@ -338,7 +338,10 @@ func (m *SimplePriceMatching) BuyToPrice(price fixedpoint.Value) (closedOrders [
|
|||
// is it a taker order?
|
||||
// higher than the current price, then it's a taker order
|
||||
if o.Price.Compare(price) >= 0 {
|
||||
// taker order, move it to the closed order
|
||||
// limit buy taker order, move it to the closed order
|
||||
// we assume that we have no price slippage here, so the latest price will be the executed price
|
||||
// TODO: simulate slippage here
|
||||
o.Price = price
|
||||
o.ExecutedQuantity = o.Quantity
|
||||
o.Status = types.OrderStatusFilled
|
||||
closedOrders = append(closedOrders, o)
|
||||
|
@ -358,7 +361,7 @@ func (m *SimplePriceMatching) BuyToPrice(price fixedpoint.Value) (closedOrders [
|
|||
|
||||
case types.OrderTypeStopMarket:
|
||||
// should we trigger the order
|
||||
if price.Compare(o.StopPrice) <= 0 {
|
||||
if price.Compare(o.StopPrice) < 0 {
|
||||
// not triggering it, put it back
|
||||
askOrders = append(askOrders, o)
|
||||
break
|
||||
|
@ -372,7 +375,7 @@ func (m *SimplePriceMatching) BuyToPrice(price fixedpoint.Value) (closedOrders [
|
|||
|
||||
case types.OrderTypeStopLimit:
|
||||
// should we trigger the order?
|
||||
if price.Compare(o.StopPrice) <= 0 {
|
||||
if price.Compare(o.StopPrice) < 0 {
|
||||
askOrders = append(askOrders, o)
|
||||
break
|
||||
}
|
||||
|
@ -381,11 +384,11 @@ func (m *SimplePriceMatching) BuyToPrice(price fixedpoint.Value) (closedOrders [
|
|||
|
||||
// is it a taker order?
|
||||
// higher than the current price, then it's a taker order
|
||||
if o.Price.Compare(price) >= 0 {
|
||||
// price protection, added by @zenix
|
||||
if o.Price.Compare(m.LastKLine.Low) < 0 {
|
||||
o.Price = m.LastKLine.Low
|
||||
}
|
||||
if o.Price.Compare(price) <= 0 {
|
||||
// limit sell order as taker, move it to the closed order
|
||||
// we assume that we have no price slippage here, so the latest price will be the executed price
|
||||
// TODO: simulate slippage here
|
||||
o.Price = price
|
||||
|
||||
o.ExecutedQuantity = o.Quantity
|
||||
o.Status = types.OrderStatusFilled
|
||||
|
@ -397,9 +400,6 @@ func (m *SimplePriceMatching) BuyToPrice(price fixedpoint.Value) (closedOrders [
|
|||
|
||||
case types.OrderTypeLimit, types.OrderTypeLimitMaker:
|
||||
if price.Compare(o.Price) >= 0 {
|
||||
if o.Price.Compare(m.LastKLine.Low) < 0 {
|
||||
o.Price = m.LastKLine.Low
|
||||
}
|
||||
o.ExecutedQuantity = o.Quantity
|
||||
o.Status = types.OrderStatusFilled
|
||||
closedOrders = append(closedOrders, o)
|
||||
|
@ -432,40 +432,44 @@ func (m *SimplePriceMatching) BuyToPrice(price fixedpoint.Value) (closedOrders [
|
|||
return closedOrders, trades
|
||||
}
|
||||
|
||||
// SellToPrice simulates the price trend in down direction.
|
||||
// When price goes down, buy orders should be executed, and the stop orders should be triggered.
|
||||
func (m *SimplePriceMatching) SellToPrice(price fixedpoint.Value) (closedOrders []types.Order, trades []types.Trade) {
|
||||
klineMatchingLogger.Debugf("kline sell to price %s", price.String())
|
||||
|
||||
var sellPrice = price
|
||||
|
||||
// in this section we handle --- the price goes lower, and we trigger the stop sell
|
||||
var askOrders []types.Order
|
||||
for _, o := range m.askOrders {
|
||||
switch o.Type {
|
||||
|
||||
case types.OrderTypeStopMarket:
|
||||
// should we trigger the order
|
||||
if o.StopPrice.Compare(sellPrice) >= 0 {
|
||||
o.Type = types.OrderTypeMarket
|
||||
o.ExecutedQuantity = o.Quantity
|
||||
o.Price = sellPrice
|
||||
o.Status = types.OrderStatusFilled
|
||||
closedOrders = append(closedOrders, o)
|
||||
} else {
|
||||
if price.Compare(o.StopPrice) > 0 {
|
||||
askOrders = append(askOrders, o)
|
||||
break
|
||||
}
|
||||
|
||||
o.Type = types.OrderTypeMarket
|
||||
o.ExecutedQuantity = o.Quantity
|
||||
o.Price = price
|
||||
o.Status = types.OrderStatusFilled
|
||||
closedOrders = append(closedOrders, o)
|
||||
|
||||
case types.OrderTypeStopLimit:
|
||||
// if the price is lower than the stop price
|
||||
// we should trigger the stop sell order
|
||||
if sellPrice.Compare(o.StopPrice) > 0 {
|
||||
if price.Compare(o.StopPrice) > 0 {
|
||||
askOrders = append(askOrders, o)
|
||||
break
|
||||
}
|
||||
|
||||
o.Type = types.OrderTypeLimit
|
||||
|
||||
// handle TAKER SELL
|
||||
// if the order price is lower than the current price
|
||||
// it's a taker order
|
||||
if o.Price.Compare(sellPrice) <= 0 {
|
||||
if o.Price.Compare(price) <= 0 {
|
||||
o.Price = price
|
||||
o.ExecutedQuantity = o.Quantity
|
||||
o.Status = types.OrderStatusFilled
|
||||
closedOrders = append(closedOrders, o)
|
||||
|
@ -485,38 +489,39 @@ func (m *SimplePriceMatching) SellToPrice(price fixedpoint.Value) (closedOrders
|
|||
|
||||
case types.OrderTypeStopMarket:
|
||||
// should we trigger the order
|
||||
if o.StopPrice.Compare(sellPrice) >= 0 {
|
||||
o.Type = types.OrderTypeMarket
|
||||
if o.StopPrice.Compare(price) < 0 {
|
||||
bidOrders = append(bidOrders, o)
|
||||
break
|
||||
}
|
||||
|
||||
o.Type = types.OrderTypeMarket
|
||||
o.ExecutedQuantity = o.Quantity
|
||||
o.Price = price
|
||||
o.Status = types.OrderStatusFilled
|
||||
closedOrders = append(closedOrders, o)
|
||||
|
||||
case types.OrderTypeStopLimit:
|
||||
// if the price is lower than the stop order price
|
||||
// we should trigger the stop order
|
||||
if o.StopPrice.Compare(price) < 0 {
|
||||
bidOrders = append(bidOrders, o)
|
||||
break
|
||||
}
|
||||
|
||||
o.Type = types.OrderTypeLimit
|
||||
|
||||
// taker order?
|
||||
if o.Price.Compare(price) >= 0 {
|
||||
o.Price = price
|
||||
o.ExecutedQuantity = o.Quantity
|
||||
o.Price = sellPrice
|
||||
o.Status = types.OrderStatusFilled
|
||||
closedOrders = append(closedOrders, o)
|
||||
} else {
|
||||
bidOrders = append(bidOrders, o)
|
||||
}
|
||||
|
||||
case types.OrderTypeStopLimit:
|
||||
// if the price is lower than the stop price
|
||||
// we should trigger the stop order
|
||||
if sellPrice.Compare(o.StopPrice) <= 0 {
|
||||
o.Type = types.OrderTypeLimit
|
||||
|
||||
if o.Price.Compare(sellPrice) <= 0 {
|
||||
if o.Price.Compare(m.LastKLine.High) > 0 {
|
||||
o.Price = m.LastKLine.High
|
||||
}
|
||||
o.ExecutedQuantity = o.Quantity
|
||||
o.Status = types.OrderStatusFilled
|
||||
closedOrders = append(closedOrders, o)
|
||||
} else {
|
||||
bidOrders = append(bidOrders, o)
|
||||
}
|
||||
} else {
|
||||
bidOrders = append(bidOrders, o)
|
||||
}
|
||||
|
||||
case types.OrderTypeLimit, types.OrderTypeLimitMaker:
|
||||
if sellPrice.Compare(o.Price) <= 0 {
|
||||
if price.Compare(o.Price) <= 0 {
|
||||
o.ExecutedQuantity = o.Quantity
|
||||
o.Status = types.OrderStatusFilled
|
||||
closedOrders = append(closedOrders, o)
|
||||
|
@ -570,7 +575,6 @@ func (m *SimplePriceMatching) getOrder(orderID uint64) (types.Order, bool) {
|
|||
|
||||
func (m *SimplePriceMatching) processKLine(kline types.KLine) {
|
||||
m.CurrentTime = kline.EndTime.Time()
|
||||
m.LastKLine = kline
|
||||
if m.LastPrice.IsZero() {
|
||||
m.LastPrice = kline.Open
|
||||
} else {
|
||||
|
@ -610,8 +614,9 @@ func (m *SimplePriceMatching) processKLine(kline types.KLine) {
|
|||
if m.LastPrice.IsZero() {
|
||||
m.BuyToPrice(kline.Close)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
m.LastKLine = kline
|
||||
}
|
||||
|
||||
func (m *SimplePriceMatching) newOrder(o types.SubmitOrder, orderID uint64) types.Order {
|
||||
|
|
|
@ -187,7 +187,7 @@ func TestSimplePriceMatching_StopLimitOrderBuy(t *testing.T) {
|
|||
LastPrice: fixedpoint.NewFromFloat(19000.0),
|
||||
}
|
||||
|
||||
stopOrder := types.SubmitOrder{
|
||||
stopBuyOrder := types.SubmitOrder{
|
||||
Symbol: market.Symbol,
|
||||
Side: types.SideTypeBuy,
|
||||
Type: types.OrderTypeStopLimit,
|
||||
|
@ -196,14 +196,24 @@ func TestSimplePriceMatching_StopLimitOrderBuy(t *testing.T) {
|
|||
StopPrice: fixedpoint.NewFromFloat(21000.0),
|
||||
TimeInForce: types.TimeInForceGTC,
|
||||
}
|
||||
createdOrder, trade, err := engine.PlaceOrder(stopOrder)
|
||||
createdOrder, trade, err := engine.PlaceOrder(stopBuyOrder)
|
||||
assert.NoError(t, err)
|
||||
assert.Nil(t, trade, "place stop order should not trigger the stop buy")
|
||||
assert.NotNil(t, createdOrder, "place stop order should not trigger the stop buy")
|
||||
|
||||
// place some limit orders, so we ensure that the remaining orders are not removed.
|
||||
_, _, err = engine.PlaceOrder(newLimitOrder(market.Symbol, types.SideTypeBuy, 18000, 0.01))
|
||||
assert.NoError(t, err)
|
||||
_, _, err = engine.PlaceOrder(newLimitOrder(market.Symbol, types.SideTypeSell, 32000, 0.01))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 2, len(engine.bidOrders))
|
||||
assert.Equal(t, 1, len(engine.askOrders))
|
||||
|
||||
closedOrders, trades := engine.BuyToPrice(fixedpoint.NewFromFloat(20000.0))
|
||||
assert.Len(t, closedOrders, 0, "price change far from the price should not trigger the stop buy")
|
||||
assert.Len(t, trades, 0, "price change far from the price should not trigger the stop buy")
|
||||
assert.Equal(t, 2, len(engine.bidOrders), "bid orders should be the same")
|
||||
assert.Equal(t, 1, len(engine.askOrders), "ask orders should be the same")
|
||||
|
||||
closedOrders, trades = engine.BuyToPrice(fixedpoint.NewFromFloat(21001.0))
|
||||
assert.Len(t, closedOrders, 1, "should trigger the stop buy order")
|
||||
|
@ -211,7 +221,30 @@ func TestSimplePriceMatching_StopLimitOrderBuy(t *testing.T) {
|
|||
|
||||
assert.Equal(t, types.OrderStatusFilled, closedOrders[0].Status)
|
||||
assert.Equal(t, types.OrderTypeLimit, closedOrders[0].Type)
|
||||
assert.Equal(t, stopOrder.Price, trades[0].Price)
|
||||
assert.Equal(t, "21001", trades[0].Price.String())
|
||||
assert.Equal(t, "21001", closedOrders[0].Price.String(), "order.Price should be adjusted")
|
||||
|
||||
assert.Equal(t, fixedpoint.NewFromFloat(21001.0).String(), engine.LastPrice.String())
|
||||
|
||||
stopOrder2 := types.SubmitOrder{
|
||||
Symbol: market.Symbol,
|
||||
Side: types.SideTypeBuy,
|
||||
Type: types.OrderTypeStopLimit,
|
||||
Quantity: fixedpoint.NewFromFloat(0.1),
|
||||
Price: fixedpoint.NewFromFloat(22000.0),
|
||||
StopPrice: fixedpoint.NewFromFloat(21000.0),
|
||||
TimeInForce: types.TimeInForceGTC,
|
||||
}
|
||||
createdOrder, trade, err = engine.PlaceOrder(stopOrder2)
|
||||
assert.NoError(t, err)
|
||||
assert.Nil(t, trade, "place stop order should not trigger the stop buy")
|
||||
assert.NotNil(t, createdOrder, "place stop order should not trigger the stop buy")
|
||||
assert.Len(t, engine.bidOrders, 2)
|
||||
|
||||
closedOrders, trades = engine.SellToPrice(fixedpoint.NewFromFloat(20500.0))
|
||||
assert.Len(t, closedOrders, 1, "should trigger the stop buy order")
|
||||
assert.Len(t, trades, 1, "should have stop order trade executed")
|
||||
assert.Len(t, engine.bidOrders, 1, "should left one bid order")
|
||||
}
|
||||
|
||||
func TestSimplePriceMatching_StopLimitOrderSell(t *testing.T) {
|
||||
|
@ -224,7 +257,7 @@ func TestSimplePriceMatching_StopLimitOrderSell(t *testing.T) {
|
|||
LastPrice: fixedpoint.NewFromFloat(22000.0),
|
||||
}
|
||||
|
||||
stopOrder := types.SubmitOrder{
|
||||
stopSellOrder := types.SubmitOrder{
|
||||
Symbol: market.Symbol,
|
||||
Side: types.SideTypeSell,
|
||||
Type: types.OrderTypeStopLimit,
|
||||
|
@ -233,22 +266,62 @@ func TestSimplePriceMatching_StopLimitOrderSell(t *testing.T) {
|
|||
StopPrice: fixedpoint.NewFromFloat(21000.0),
|
||||
TimeInForce: types.TimeInForceGTC,
|
||||
}
|
||||
createdOrder, trade, err := engine.PlaceOrder(stopOrder)
|
||||
createdOrder, trade, err := engine.PlaceOrder(stopSellOrder)
|
||||
assert.NoError(t, err)
|
||||
assert.Nil(t, trade, "place stop order should not trigger the stop sell")
|
||||
assert.NotNil(t, createdOrder, "place stop order should not trigger the stop sell")
|
||||
|
||||
// place some limit orders, so we ensure that the remaining orders are not removed.
|
||||
_, _, err = engine.PlaceOrder(newLimitOrder(market.Symbol, types.SideTypeBuy, 18000, 0.01))
|
||||
assert.NoError(t, err)
|
||||
_, _, err = engine.PlaceOrder(newLimitOrder(market.Symbol, types.SideTypeSell, 32000, 0.01))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(engine.bidOrders))
|
||||
assert.Equal(t, 2, len(engine.askOrders))
|
||||
|
||||
closedOrders, trades := engine.SellToPrice(fixedpoint.NewFromFloat(21500.0))
|
||||
assert.Len(t, closedOrders, 0, "price change far from the price should not trigger the stop buy")
|
||||
assert.Len(t, trades, 0, "price change far from the price should not trigger the stop buy")
|
||||
assert.Equal(t, 1, len(engine.bidOrders))
|
||||
assert.Equal(t, 2, len(engine.askOrders))
|
||||
|
||||
closedOrders, trades = engine.SellToPrice(fixedpoint.NewFromFloat(20990.0))
|
||||
assert.Len(t, closedOrders, 1, "should trigger the stop sell order")
|
||||
assert.Len(t, trades, 1, "should have stop order trade executed")
|
||||
assert.Equal(t, 1, len(engine.bidOrders))
|
||||
assert.Equal(t, 1, len(engine.askOrders))
|
||||
|
||||
assert.Equal(t, types.OrderStatusFilled, closedOrders[0].Status)
|
||||
assert.Equal(t, types.OrderTypeLimit, closedOrders[0].Type)
|
||||
assert.Equal(t, stopOrder.Price, trades[0].Price)
|
||||
assert.Equal(t, "20990", closedOrders[0].Price.String())
|
||||
assert.Equal(t, "20990", trades[0].Price.String())
|
||||
assert.Equal(t, "20990", engine.LastPrice.String())
|
||||
|
||||
// place a stop limit sell order with a higher price than the current price
|
||||
stopOrder2 := types.SubmitOrder{
|
||||
Symbol: market.Symbol,
|
||||
Side: types.SideTypeSell,
|
||||
Type: types.OrderTypeStopLimit,
|
||||
Quantity: fixedpoint.NewFromFloat(0.1),
|
||||
Price: fixedpoint.NewFromFloat(20000.0),
|
||||
StopPrice: fixedpoint.NewFromFloat(21000.0),
|
||||
TimeInForce: types.TimeInForceGTC,
|
||||
}
|
||||
|
||||
createdOrder, trade, err = engine.PlaceOrder(stopOrder2)
|
||||
assert.NoError(t, err)
|
||||
assert.Nil(t, trade, "place stop order should not trigger the stop sell")
|
||||
assert.NotNil(t, createdOrder, "place stop order should not trigger the stop sell")
|
||||
|
||||
closedOrders, trades = engine.BuyToPrice(fixedpoint.NewFromFloat(21000.0))
|
||||
if assert.Len(t, closedOrders, 1, "should trigger the stop sell order") {
|
||||
assert.Len(t, trades, 1, "should have stop order trade executed")
|
||||
assert.Equal(t, types.SideTypeSell, closedOrders[0].Side)
|
||||
assert.Equal(t, types.OrderStatusFilled, closedOrders[0].Status)
|
||||
assert.Equal(t, types.OrderTypeLimit, closedOrders[0].Type)
|
||||
assert.Equal(t, "21000", trades[0].Price.String(), "trade price should be the kline price not the order price")
|
||||
assert.Equal(t, "21000", engine.LastPrice.String(), "engine last price should be updated correctly")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSimplePriceMatching_StopMarketOrderSell(t *testing.T) {
|
||||
|
@ -285,7 +358,7 @@ func TestSimplePriceMatching_StopMarketOrderSell(t *testing.T) {
|
|||
|
||||
assert.Equal(t, types.OrderStatusFilled, closedOrders[0].Status)
|
||||
assert.Equal(t, types.OrderTypeMarket, closedOrders[0].Type)
|
||||
assert.Equal(t, fixedpoint.NewFromFloat(20990.0), trades[0].Price)
|
||||
assert.Equal(t, fixedpoint.NewFromFloat(20990.0), trades[0].Price, "trade price should be adjusted to the last price")
|
||||
}
|
||||
|
||||
func TestSimplePriceMatching_PlaceLimitOrder(t *testing.T) {
|
||||
|
|
Loading…
Reference in New Issue
Block a user