mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-25 16:25:16 +00:00
bollmaker: check dust order for stop
This commit is contained in:
parent
99af5d3971
commit
2255f3ed0a
|
@ -109,7 +109,6 @@ func (m *SimplePriceMatching) CancelOrder(o types.Order) (types.Order, error) {
|
|||
}
|
||||
|
||||
func (m *SimplePriceMatching) PlaceOrder(o types.SubmitOrder) (closedOrders *types.Order, trades *types.Trade, err error) {
|
||||
|
||||
// price for checking account balance
|
||||
price := o.Price
|
||||
switch o.Type {
|
||||
|
@ -119,16 +118,23 @@ func (m *SimplePriceMatching) PlaceOrder(o types.SubmitOrder) (closedOrders *typ
|
|||
price = o.Price
|
||||
}
|
||||
|
||||
if o.Quantity < m.Market.MinQuantity {
|
||||
return nil, nil, fmt.Errorf("order quantity %f is less than minQuantity %f, order: %+v", o.Quantity, m.Market.MinQuantity, o)
|
||||
}
|
||||
|
||||
quoteQuantity := o.Quantity * price
|
||||
if quoteQuantity < m.Market.MinNotional {
|
||||
return nil, nil, fmt.Errorf("order amount %f is less than minNotional %f, order: %+v", quoteQuantity, m.Market.MinNotional, o)
|
||||
}
|
||||
|
||||
switch o.Side {
|
||||
case types.SideTypeBuy:
|
||||
quote := price * o.Quantity
|
||||
if err := m.Account.LockBalance(m.Market.QuoteCurrency, fixedpoint.NewFromFloat(quote)); err != nil {
|
||||
if err := m.Account.LockBalance(m.Market.QuoteCurrency, fixedpoint.NewFromFloat(quoteQuantity)); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
case types.SideTypeSell:
|
||||
baseQuantity := o.Quantity
|
||||
if err := m.Account.LockBalance(m.Market.BaseCurrency, fixedpoint.NewFromFloat(baseQuantity)); err != nil {
|
||||
if err := m.Account.LockBalance(m.Market.BaseCurrency, fixedpoint.NewFromFloat(o.Quantity)); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
@ -428,7 +434,7 @@ func (m *SimplePriceMatching) processKLine(kline types.KLine) {
|
|||
m.BuyToPrice(fixedpoint.NewFromFloat(kline.Close))
|
||||
}
|
||||
default: // no trade up or down
|
||||
if (m.LastPrice == 0) {
|
||||
if m.LastPrice == 0 {
|
||||
m.BuyToPrice(fixedpoint.NewFromFloat(kline.Close))
|
||||
}
|
||||
|
||||
|
|
|
@ -175,19 +175,17 @@ func (trader *Trader) SetRiskControls(riskControls *RiskControls) {
|
|||
trader.riskControls = riskControls
|
||||
}
|
||||
|
||||
|
||||
func (trader *Trader) Subscribe() {
|
||||
// pre-subscribe the data
|
||||
for sessionName, strategies := range trader.exchangeStrategies {
|
||||
session := trader.environment.sessions[sessionName]
|
||||
for _, strategy := range strategies {
|
||||
for _, strategy := range strategies {
|
||||
if initializer, ok := strategy.(StrategyInitializer); ok {
|
||||
initializer.Initialize()
|
||||
if err := initializer.Initialize(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if subscriber, ok := strategy.(ExchangeSessionSubscriber); ok {
|
||||
subscriber.Subscribe(session)
|
||||
} else {
|
||||
|
@ -197,9 +195,9 @@ func (trader *Trader) Subscribe() {
|
|||
}
|
||||
|
||||
for _, strategy := range trader.crossExchangeStrategies {
|
||||
for _, strategy := range strategies {
|
||||
if initializer, ok := strategy.(StrategyInitializer); ok {
|
||||
initializer.Initialize()
|
||||
if err := initializer.Initialize(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -90,9 +90,10 @@ func (c *TrailingStopController) Subscribe(session *bbgo.ExchangeSession) {
|
|||
})
|
||||
}
|
||||
|
||||
func (c *TrailingStopController) Setup(ctx context.Context, session *bbgo.ExchangeSession, tradeCollector *bbgo.TradeCollector) {
|
||||
func (c *TrailingStopController) Run(ctx context.Context, session *bbgo.ExchangeSession, tradeCollector *bbgo.TradeCollector) {
|
||||
// store the position
|
||||
c.position = tradeCollector.Position()
|
||||
c.averageCost = c.position.AverageCost
|
||||
|
||||
// Use trade collector to get the position update event
|
||||
tradeCollector.OnPositionUpdate(func(position *types.Position) {
|
||||
|
@ -101,11 +102,7 @@ func (c *TrailingStopController) Setup(ctx context.Context, session *bbgo.Exchan
|
|||
})
|
||||
|
||||
session.MarketDataStream.OnKLineClosed(func(kline types.KLine) {
|
||||
if kline.Symbol != c.Symbol {
|
||||
return
|
||||
}
|
||||
|
||||
if kline.Interval != c.Interval {
|
||||
if kline.Symbol != c.Symbol || kline.Interval != c.Interval {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -115,7 +112,13 @@ func (c *TrailingStopController) Setup(ctx context.Context, session *bbgo.Exchan
|
|||
c.latestHigh = math.Max(closePrice, c.latestHigh)
|
||||
|
||||
if c.Virtual {
|
||||
if c.position == nil {
|
||||
// if average cost is updated, we can check min profit
|
||||
if c.averageCost == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// skip dust position
|
||||
if c.position.Base.Abs().Float64() < c.position.Market.MinQuantity || c.position.Base.Abs().Float64()*closePrice < c.position.Market.MinNotional {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -124,24 +127,21 @@ func (c *TrailingStopController) Setup(ctx context.Context, session *bbgo.Exchan
|
|||
return
|
||||
}
|
||||
|
||||
// if average cost is updated, we can check min profit
|
||||
if c.averageCost == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// if it's below the average cost, we skip stop
|
||||
if closePrice < c.averageCost.Float64() {
|
||||
return
|
||||
}
|
||||
|
||||
// if the profit rate is defined, and it is less than our minimum profit rate, we skip stop
|
||||
if c.MinProfit > 0 && changeRate(closePrice, c.averageCost.Float64()) < c.MinProfit.Float64() {
|
||||
if c.MinProfit > 0 &&
|
||||
(closePrice < c.averageCost.Float64() ||
|
||||
changeRate(closePrice, c.averageCost.Float64()) < c.MinProfit.Float64()) {
|
||||
return
|
||||
}
|
||||
|
||||
log.Infof("trailing stop event emitted, submitting market order to stop...")
|
||||
marketOrder := c.position.NewClosePositionOrder(c.ClosePosition.Float64())
|
||||
if marketOrder != nil {
|
||||
// skip dust order
|
||||
if marketOrder.Quantity*closePrice < c.position.Market.MinNotional {
|
||||
return
|
||||
}
|
||||
|
||||
createdOrders, err := session.Exchange.SubmitOrders(ctx, *marketOrder)
|
||||
if err != nil {
|
||||
log.WithError(err).Errorf("stop order place error")
|
||||
|
@ -176,12 +176,16 @@ func (c *TrailingStopController) GenerateStopOrder(stopPrice, price float64) *ty
|
|||
}
|
||||
|
||||
quantity := math.Abs(base.Float64())
|
||||
quoteQuantity := price * quantity
|
||||
|
||||
if c.ClosePosition > 0 {
|
||||
quantity = quantity * c.ClosePosition.Float64()
|
||||
}
|
||||
|
||||
quantity = math.Min(quantity, c.position.Market.MinQuantity)
|
||||
// skip dust orders
|
||||
if quantity < c.position.Market.MinQuantity || quoteQuantity < c.position.Market.MinNotional {
|
||||
return nil
|
||||
}
|
||||
|
||||
side := types.SideTypeSell
|
||||
if base < 0 {
|
||||
|
@ -317,10 +321,11 @@ func (s *Strategy) ID() string {
|
|||
return ID
|
||||
}
|
||||
|
||||
func (s *Strategy) Initialize(session *bbgo.ExchangeSession) error {
|
||||
func (s *Strategy) Initialize() error {
|
||||
for _, stop := range s.Stops {
|
||||
s.stopControllers = append(s.stopControllers,
|
||||
NewTrailingStopController(s.Symbol, stop.TrailingStop))
|
||||
NewTrailingStopController(s.Symbol, stop.TrailingStop),
|
||||
)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -714,6 +719,10 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se
|
|||
|
||||
s.tradeCollector.BindStream(session.UserDataStream)
|
||||
|
||||
for _, stopController := range s.stopControllers {
|
||||
stopController.Run(ctx, session, s.tradeCollector)
|
||||
}
|
||||
|
||||
session.UserDataStream.OnStart(func() {
|
||||
if s.UseTickerPrice {
|
||||
ticker, err := s.session.Exchange.QueryTicker(ctx, s.Symbol)
|
||||
|
|
|
@ -127,10 +127,20 @@ type SubmitOrder struct {
|
|||
}
|
||||
|
||||
func (o SubmitOrder) String() string {
|
||||
switch o.Type {
|
||||
case OrderTypeMarket:
|
||||
return fmt.Sprintf("SubmitOrder %s %s %s %f", o.Symbol, o.Type, o.Side, o.Quantity)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("SubmitOrder %s %s %s %f @ %f", o.Symbol, o.Type, o.Side, o.Quantity, o.Price)
|
||||
}
|
||||
|
||||
func (o SubmitOrder) PlainText() string {
|
||||
switch o.Type {
|
||||
case OrderTypeMarket:
|
||||
return fmt.Sprintf("SubmitOrder %s %s %s %f", o.Symbol, o.Type, o.Side, o.Quantity)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("SubmitOrder %s %s %s %f @ %f", o.Symbol, o.Type, o.Side, o.Quantity, o.Price)
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ package types
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
@ -58,7 +57,10 @@ func (p *Position) NewClosePositionOrder(percentage float64) *SubmitOrder {
|
|||
base := p.GetBase()
|
||||
quantity := base.Float64()
|
||||
quantity = quantity * percentage
|
||||
quantity = math.Min(quantity, p.Market.MinQuantity)
|
||||
if quantity < p.Market.MinQuantity {
|
||||
return nil
|
||||
}
|
||||
|
||||
side := SideTypeSell
|
||||
if base == 0 {
|
||||
return nil
|
||||
|
|
Loading…
Reference in New Issue
Block a user