mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-10 09:11:55 +00:00
remain only template part
This commit is contained in:
parent
aea3abae07
commit
800148b271
30
config/dca2.yaml
Normal file
30
config/dca2.yaml
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
---
|
||||||
|
backtest:
|
||||||
|
startTime: "2023-06-01"
|
||||||
|
endTime: "2023-07-01"
|
||||||
|
sessions:
|
||||||
|
- max
|
||||||
|
symbols:
|
||||||
|
- ETHUSDT
|
||||||
|
accounts:
|
||||||
|
binance:
|
||||||
|
balances:
|
||||||
|
USDT: 20_000.0
|
||||||
|
|
||||||
|
persistence:
|
||||||
|
redis:
|
||||||
|
host: 127.0.0.1
|
||||||
|
port: 6379
|
||||||
|
db: 0
|
||||||
|
|
||||||
|
exchangeStrategies:
|
||||||
|
|
||||||
|
- on: max
|
||||||
|
dca2:
|
||||||
|
symbol: ETHUSDT
|
||||||
|
short: false
|
||||||
|
budget: 5000
|
||||||
|
maxOrderNum: 10
|
||||||
|
priceDeviation: 1%
|
||||||
|
takeProfitRatio: 1%
|
||||||
|
coolDownInterval: 5m
|
|
@ -96,6 +96,9 @@ func (s *Stream) handleConnect() {
|
||||||
case types.DepthLevelMedium:
|
case types.DepthLevelMedium:
|
||||||
depth = 20
|
depth = 20
|
||||||
|
|
||||||
|
case types.DepthLevel1:
|
||||||
|
depth = 1
|
||||||
|
|
||||||
case types.DepthLevel5:
|
case types.DepthLevel5:
|
||||||
depth = 5
|
depth = 5
|
||||||
|
|
||||||
|
|
|
@ -69,9 +69,11 @@ func (s *Strategy) Initialize(ctx context.Context, environ *bbgo.Environment, se
|
||||||
s.OrderExecutor.BindEnvironment(environ)
|
s.OrderExecutor.BindEnvironment(environ)
|
||||||
s.OrderExecutor.BindProfitStats(s.ProfitStats)
|
s.OrderExecutor.BindProfitStats(s.ProfitStats)
|
||||||
s.OrderExecutor.Bind()
|
s.OrderExecutor.Bind()
|
||||||
|
/*
|
||||||
s.OrderExecutor.TradeCollector().OnPositionUpdate(func(position *types.Position) {
|
s.OrderExecutor.TradeCollector().OnPositionUpdate(func(position *types.Position) {
|
||||||
// bbgo.Sync(ctx, s)
|
bbgo.Sync(ctx, s)
|
||||||
})
|
})
|
||||||
|
*/
|
||||||
|
|
||||||
if !s.PositionHardLimit.IsZero() && !s.MaxPositionQuantity.IsZero() {
|
if !s.PositionHardLimit.IsZero() && !s.MaxPositionQuantity.IsZero() {
|
||||||
log.Infof("positionHardLimit and maxPositionQuantity are configured, setting up PositionRiskControl...")
|
log.Infof("positionHardLimit and maxPositionQuantity are configured, setting up PositionRiskControl...")
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
package dca2
|
|
19
pkg/strategy/dca2/debug.go
Normal file
19
pkg/strategy/dca2/debug.go
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
package dca2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Strategy) debugOrders(submitOrders []types.Order) {
|
||||||
|
var sb strings.Builder
|
||||||
|
sb.WriteString("DCA ORDERS[\n")
|
||||||
|
for i, order := range submitOrders {
|
||||||
|
sb.WriteString(fmt.Sprintf("%3d) ", i+1) + order.String() + "\n")
|
||||||
|
}
|
||||||
|
sb.WriteString("] END OF DCA ORDERS")
|
||||||
|
|
||||||
|
s.logger.Info(sb.String())
|
||||||
|
}
|
|
@ -3,10 +3,15 @@ package dca2
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/c9s/bbgo/pkg/bbgo"
|
"github.com/c9s/bbgo/pkg/bbgo"
|
||||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||||
|
"github.com/c9s/bbgo/pkg/strategy/common"
|
||||||
"github.com/c9s/bbgo/pkg/types"
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
|
"github.com/c9s/bbgo/pkg/util"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -21,17 +26,20 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Strategy struct {
|
type Strategy struct {
|
||||||
|
*common.Strategy
|
||||||
|
|
||||||
Environment *bbgo.Environment
|
Environment *bbgo.Environment
|
||||||
Market types.Market
|
Market types.Market
|
||||||
|
|
||||||
Symbol string `json:"symbol"`
|
Symbol string `json:"symbol"`
|
||||||
|
|
||||||
// setting
|
// setting
|
||||||
|
Short bool `json:"short"`
|
||||||
Budget fixedpoint.Value `json:"budget"`
|
Budget fixedpoint.Value `json:"budget"`
|
||||||
OrderNum int64 `json:"orderNum"`
|
MaxOrderNum int64 `json:"maxOrderNum"`
|
||||||
Margin fixedpoint.Value `json:"margin"`
|
PriceDeviation fixedpoint.Value `json:"priceDeviation"`
|
||||||
TakeProfitSpread fixedpoint.Value `json:"takeProfitSpread"`
|
TakeProfitRatio fixedpoint.Value `json:"takeProfitRatio"`
|
||||||
RoundInterval types.Duration `json:"roundInterval"`
|
CoolDownInterval types.Duration `json:"coolDownInterval"`
|
||||||
|
|
||||||
// OrderGroupID is the group ID used for the strategy instance for canceling orders
|
// OrderGroupID is the group ID used for the strategy instance for canceling orders
|
||||||
OrderGroupID uint32 `json:"orderGroupID"`
|
OrderGroupID uint32 `json:"orderGroupID"`
|
||||||
|
@ -40,14 +48,12 @@ type Strategy struct {
|
||||||
logger *logrus.Entry
|
logger *logrus.Entry
|
||||||
LogFields logrus.Fields `json:"logFields"`
|
LogFields logrus.Fields `json:"logFields"`
|
||||||
|
|
||||||
// persistence fields: position and profit
|
|
||||||
Position *types.Position `persistence:"position"`
|
|
||||||
ProfitStats *types.ProfitStats `persistence:"profit_stats"`
|
|
||||||
|
|
||||||
// private field
|
// private field
|
||||||
session *bbgo.ExchangeSession
|
mu sync.Mutex
|
||||||
orderExecutor *bbgo.GeneralOrderExecutor
|
makerSide types.SideType
|
||||||
book *types.StreamOrderBook
|
takeProfitSide types.SideType
|
||||||
|
takeProfitPrice fixedpoint.Value
|
||||||
|
startTimeOfNextRound time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Strategy) ID() string {
|
func (s *Strategy) ID() string {
|
||||||
|
@ -55,15 +61,15 @@ func (s *Strategy) ID() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Strategy) Validate() error {
|
func (s *Strategy) Validate() error {
|
||||||
if s.OrderNum < 1 {
|
if s.MaxOrderNum < 1 {
|
||||||
return fmt.Errorf("maxOrderNum can not be < 1")
|
return fmt.Errorf("maxOrderNum can not be < 1")
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.TakeProfitSpread.Compare(fixedpoint.Zero) <= 0 {
|
if s.TakeProfitRatio.Sign() <= 0 {
|
||||||
return fmt.Errorf("takeProfitSpread can not be <= 0")
|
return fmt.Errorf("takeProfitSpread can not be <= 0")
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.Margin.Compare(fixedpoint.Zero) <= 0 {
|
if s.PriceDeviation.Sign() <= 0 {
|
||||||
return fmt.Errorf("margin can not be <= 0")
|
return fmt.Errorf("margin can not be <= 0")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,95 +97,52 @@ func (s *Strategy) InstanceID() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
|
func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {
|
||||||
session.Subscribe(types.BookChannel, s.Symbol, types.SubscribeOptions{Depth: types.DepthLevel1})
|
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: types.Interval1m})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Strategy) Run(ctx context.Context, _ bbgo.OrderExecutor, session *bbgo.ExchangeSession) error {
|
func (s *Strategy) Run(ctx context.Context, _ bbgo.OrderExecutor, session *bbgo.ExchangeSession) error {
|
||||||
if s.Position == nil {
|
s.Strategy = &common.Strategy{}
|
||||||
s.Position = types.NewPositionFromMarket(s.Market)
|
s.Strategy.Initialize(ctx, s.Environment, session, s.Market, ID, s.InstanceID())
|
||||||
}
|
|
||||||
|
|
||||||
if s.ProfitStats == nil {
|
|
||||||
s.ProfitStats = types.NewProfitStats(s.Market)
|
|
||||||
}
|
|
||||||
|
|
||||||
instanceID := s.InstanceID()
|
instanceID := s.InstanceID()
|
||||||
s.session = session
|
|
||||||
s.orderExecutor = bbgo.NewGeneralOrderExecutor(session, s.Symbol, ID, instanceID, s.Position)
|
|
||||||
s.orderExecutor.BindEnvironment(s.Environment)
|
|
||||||
s.orderExecutor.BindProfitStats(s.ProfitStats)
|
|
||||||
s.orderExecutor.TradeCollector().OnPositionUpdate(func(position *types.Position) {
|
|
||||||
bbgo.Sync(ctx, s)
|
|
||||||
})
|
|
||||||
s.orderExecutor.Bind()
|
|
||||||
s.book = types.NewStreamBook(s.Symbol)
|
|
||||||
s.book.BindStream(s.session.MarketDataStream)
|
|
||||||
|
|
||||||
balances := session.GetAccount().Balances()
|
if s.Short {
|
||||||
|
s.makerSide = types.SideTypeSell
|
||||||
|
s.takeProfitSide = types.SideTypeBuy
|
||||||
|
} else {
|
||||||
|
s.makerSide = types.SideTypeBuy
|
||||||
|
s.takeProfitSide = types.SideTypeSell
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.OrderGroupID == 0 {
|
||||||
|
s.OrderGroupID = util.FNV32(instanceID) % math.MaxInt32
|
||||||
|
}
|
||||||
|
|
||||||
|
// order executor
|
||||||
|
s.OrderExecutor.TradeCollector().OnPositionUpdate(func(position *types.Position) {
|
||||||
|
s.logger.Infof("position: %s", s.Position.String())
|
||||||
|
bbgo.Sync(ctx, s)
|
||||||
|
|
||||||
|
// update take profit price here
|
||||||
|
})
|
||||||
|
|
||||||
|
session.MarketDataStream.OnKLine(func(kline types.KLine) {
|
||||||
|
// check price here
|
||||||
|
})
|
||||||
|
|
||||||
|
session.UserDataStream.OnAuth(func() {
|
||||||
|
s.logger.Info("user data stream authenticated, start the process")
|
||||||
|
// decide state here
|
||||||
|
})
|
||||||
|
|
||||||
|
balances, err := session.Exchange.QueryAccountBalances(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
balance := balances[s.Market.QuoteCurrency]
|
balance := balances[s.Market.QuoteCurrency]
|
||||||
if balance.Available.Compare(s.Budget) < 0 {
|
if balance.Available.Compare(s.Budget) < 0 {
|
||||||
return fmt.Errorf("the available balance of %s is %s which is less than budget setting %s, please check it", s.Market.QuoteCurrency, balance.Available, s.Budget)
|
return fmt.Errorf("the available balance of %s is %s which is less than budget setting %s, please check it", s.Market.QuoteCurrency, balance.Available, s.Budget)
|
||||||
}
|
}
|
||||||
|
|
||||||
session.MarketDataStream.OnBookUpdate(func(book types.SliceOrderBook) {
|
|
||||||
bid, ok := book.BestBid()
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
takeProfitPrice := s.Market.TruncatePrice(s.Position.AverageCost.Mul(fixedpoint.One.Add(s.TakeProfitSpread)))
|
|
||||||
if bid.Price.Compare(takeProfitPrice) >= 0 {
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Strategy) generateMakerOrder(budget, askPrice, margin fixedpoint.Value, orderNum int64) ([]types.SubmitOrder, error) {
|
|
||||||
marginPrice := askPrice.Mul(margin)
|
|
||||||
price := askPrice
|
|
||||||
var prices []fixedpoint.Value
|
|
||||||
var total fixedpoint.Value
|
|
||||||
for i := 0; i < int(orderNum); i++ {
|
|
||||||
price = price.Sub(marginPrice)
|
|
||||||
truncatePrice := s.Market.TruncatePrice(price)
|
|
||||||
prices = append(prices, truncatePrice)
|
|
||||||
total = total.Add(truncatePrice)
|
|
||||||
}
|
|
||||||
|
|
||||||
quantity := budget.Div(total)
|
|
||||||
quantity = s.Market.TruncateQuantity(quantity)
|
|
||||||
|
|
||||||
var submitOrders []types.SubmitOrder
|
|
||||||
|
|
||||||
for _, price := range prices {
|
|
||||||
submitOrders = append(submitOrders, types.SubmitOrder{
|
|
||||||
Symbol: s.Symbol,
|
|
||||||
Market: s.Market,
|
|
||||||
Type: types.OrderTypeLimit,
|
|
||||||
Price: price,
|
|
||||||
Side: types.SideTypeBuy,
|
|
||||||
TimeInForce: types.TimeInForceGTC,
|
|
||||||
Quantity: quantity,
|
|
||||||
Tag: orderTag,
|
|
||||||
GroupID: s.OrderGroupID,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return submitOrders, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Strategy) generateTakeProfitOrder(position *types.Position, takeProfitSpread fixedpoint.Value) types.SubmitOrder {
|
|
||||||
takeProfitPrice := s.Market.TruncatePrice(position.AverageCost.Mul(fixedpoint.One.Add(takeProfitSpread)))
|
|
||||||
return types.SubmitOrder{
|
|
||||||
Symbol: s.Symbol,
|
|
||||||
Market: s.Market,
|
|
||||||
Type: types.OrderTypeLimit,
|
|
||||||
Price: takeProfitPrice,
|
|
||||||
Side: types.SideTypeSell,
|
|
||||||
TimeInForce: types.TimeInForceGTC,
|
|
||||||
Quantity: position.GetBase().Abs(),
|
|
||||||
Tag: orderTag,
|
|
||||||
GroupID: s.OrderGroupID,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,141 +0,0 @@
|
||||||
package dca2
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
|
||||||
"github.com/c9s/bbgo/pkg/types"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func number(a interface{}) fixedpoint.Value {
|
|
||||||
switch v := a.(type) {
|
|
||||||
case string:
|
|
||||||
return fixedpoint.MustNewFromString(v)
|
|
||||||
case int:
|
|
||||||
return fixedpoint.NewFromInt(int64(v))
|
|
||||||
case int64:
|
|
||||||
return fixedpoint.NewFromInt(int64(v))
|
|
||||||
case float64:
|
|
||||||
return fixedpoint.NewFromFloat(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fixedpoint.Zero
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTestMarket(symbol string) types.Market {
|
|
||||||
switch symbol {
|
|
||||||
case "BTCUSDT":
|
|
||||||
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),
|
|
||||||
}
|
|
||||||
case "ETHUSDT":
|
|
||||||
return types.Market{
|
|
||||||
BaseCurrency: "ETH",
|
|
||||||
QuoteCurrency: "USDT",
|
|
||||||
TickSize: number(0.01),
|
|
||||||
StepSize: number(0.00001),
|
|
||||||
PricePrecision: 2,
|
|
||||||
VolumePrecision: 6,
|
|
||||||
MinNotional: number(8.000),
|
|
||||||
MinQuantity: number(0.0046),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// default
|
|
||||||
return types.Market{
|
|
||||||
BaseCurrency: "BTC",
|
|
||||||
QuoteCurrency: "USDT",
|
|
||||||
TickSize: number(0.01),
|
|
||||||
StepSize: number(0.00001),
|
|
||||||
PricePrecision: 2,
|
|
||||||
VolumePrecision: 8,
|
|
||||||
MinNotional: number(10.0),
|
|
||||||
MinQuantity: number(0.001),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTestStrategy(va ...string) *Strategy {
|
|
||||||
symbol := "BTCUSDT"
|
|
||||||
|
|
||||||
if len(va) > 0 {
|
|
||||||
symbol = va[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
market := newTestMarket(symbol)
|
|
||||||
s := &Strategy{
|
|
||||||
logger: logrus.NewEntry(logrus.New()),
|
|
||||||
Symbol: symbol,
|
|
||||||
Market: market,
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGenerateMakerOrder(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
strategy := newTestStrategy()
|
|
||||||
|
|
||||||
budget := number("105000")
|
|
||||||
askPrice := number("30000")
|
|
||||||
margin := number("0.05")
|
|
||||||
submitOrders, err := strategy.generateMakerOrder(budget, askPrice, margin, 4)
|
|
||||||
if !assert.NoError(err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Len(submitOrders, 4)
|
|
||||||
assert.Equal(submitOrders[0].Price, number("28500"))
|
|
||||||
assert.Equal(submitOrders[0].Quantity, number("1"))
|
|
||||||
assert.Equal(submitOrders[1].Price, number("27000"))
|
|
||||||
assert.Equal(submitOrders[1].Quantity, number("1"))
|
|
||||||
assert.Equal(submitOrders[2].Price, number("25500"))
|
|
||||||
assert.Equal(submitOrders[2].Quantity, number("1"))
|
|
||||||
assert.Equal(submitOrders[3].Price, number("24000"))
|
|
||||||
assert.Equal(submitOrders[3].Quantity, number("1"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGenerateTakeProfitOrder(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
strategy := newTestStrategy()
|
|
||||||
|
|
||||||
position := types.NewPositionFromMarket(strategy.Market)
|
|
||||||
position.AddTrade(types.Trade{
|
|
||||||
Side: types.SideTypeBuy,
|
|
||||||
Price: number("28500"),
|
|
||||||
Quantity: number("1"),
|
|
||||||
QuoteQuantity: number("28500"),
|
|
||||||
Fee: number("0.0015"),
|
|
||||||
FeeCurrency: strategy.Market.BaseCurrency,
|
|
||||||
})
|
|
||||||
|
|
||||||
o := strategy.generateTakeProfitOrder(position, number("10%"))
|
|
||||||
assert.Equal(number("31397.09"), o.Price)
|
|
||||||
assert.Equal(number("0.9985"), o.Quantity)
|
|
||||||
assert.Equal(types.SideTypeSell, o.Side)
|
|
||||||
assert.Equal(strategy.Symbol, o.Symbol)
|
|
||||||
|
|
||||||
position.AddTrade(types.Trade{
|
|
||||||
Side: types.SideTypeBuy,
|
|
||||||
Price: number("27000"),
|
|
||||||
Quantity: number("0.5"),
|
|
||||||
QuoteQuantity: number("13500"),
|
|
||||||
Fee: number("0.00075"),
|
|
||||||
FeeCurrency: strategy.Market.BaseCurrency,
|
|
||||||
})
|
|
||||||
o = strategy.generateTakeProfitOrder(position, number("10%"))
|
|
||||||
assert.Equal(number("30846.26"), o.Price)
|
|
||||||
assert.Equal(number("1.49775"), o.Quantity)
|
|
||||||
assert.Equal(types.SideTypeSell, o.Side)
|
|
||||||
assert.Equal(strategy.Symbol, o.Symbol)
|
|
||||||
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user