remain only template part

This commit is contained in:
chiahung.lin 2023-11-23 16:45:28 +08:00
parent aea3abae07
commit 800148b271
7 changed files with 114 additions and 239 deletions

30
config/dca2.yaml Normal file
View 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

View File

@ -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

View File

@ -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...")

View File

@ -1 +0,0 @@
package dca2

View 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())
}

View File

@ -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,
}
}

View File

@ -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)
}