mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-10 09:11:55 +00:00
FEATURE: ProfitStats for dca2
This commit is contained in:
parent
468b73abb6
commit
21e87079b5
|
@ -20,7 +20,7 @@ func (s *Strategy) placeOpenPositionOrders(ctx context.Context) error {
|
|||
return err
|
||||
}
|
||||
|
||||
orders, err := generateOpenPositionOrders(s.Market, s.QuoteInvestment, price, s.PriceDeviation, s.MaxOrderCount, s.OrderGroupID)
|
||||
orders, err := generateOpenPositionOrders(s.Market, s.ProfitStats.QuoteInvestment, price, s.PriceDeviation, s.MaxOrderCount, s.OrderGroupID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
package dca2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||
|
@ -11,8 +15,7 @@ type ProfitStats struct {
|
|||
Symbol string `json:"symbol"`
|
||||
Market types.Market `json:"market,omitempty"`
|
||||
|
||||
CreatedAt time.Time `json:"since,omitempty"`
|
||||
UpdatedAt time.Time `json:"updatedAt,omitempty"`
|
||||
FromOrderID uint64 `json:"fromOrderID,omitempty"`
|
||||
Round int64 `json:"round,omitempty"`
|
||||
QuoteInvestment fixedpoint.Value `json:"quoteInvestment,omitempty"`
|
||||
|
||||
|
@ -29,8 +32,6 @@ func newProfitStats(market types.Market, quoteInvestment fixedpoint.Value) *Prof
|
|||
return &ProfitStats{
|
||||
Symbol: market.Symbol,
|
||||
Market: market,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
Round: 0,
|
||||
QuoteInvestment: quoteInvestment,
|
||||
RoundFee: make(map[string]fixedpoint.Value),
|
||||
|
@ -70,21 +71,82 @@ func (s *ProfitStats) AddTrade(trade types.Trade) {
|
|||
s.TotalFee[trade.FeeCurrency] = trade.Fee
|
||||
}
|
||||
|
||||
switch trade.Side {
|
||||
case types.SideTypeSell:
|
||||
s.RoundProfit = s.RoundProfit.Add(trade.QuoteQuantity)
|
||||
s.TotalProfit = s.TotalProfit.Add(trade.QuoteQuantity)
|
||||
case types.SideTypeBuy:
|
||||
s.RoundProfit = s.RoundProfit.Sub(trade.QuoteQuantity)
|
||||
s.TotalProfit = s.TotalProfit.Sub(trade.QuoteQuantity)
|
||||
default:
|
||||
quoteQuantity := trade.QuoteQuantity
|
||||
if trade.Side == types.SideTypeBuy {
|
||||
quoteQuantity = quoteQuantity.Neg()
|
||||
}
|
||||
|
||||
s.UpdatedAt = trade.Time.Time()
|
||||
s.RoundProfit = s.RoundProfit.Add(quoteQuantity)
|
||||
s.TotalProfit = s.TotalProfit.Add(quoteQuantity)
|
||||
|
||||
if s.Market.QuoteCurrency == trade.FeeCurrency {
|
||||
s.RoundProfit.Sub(trade.Fee)
|
||||
s.TotalProfit.Sub(trade.Fee)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ProfitStats) FinishRound() {
|
||||
func (s *ProfitStats) NewRound() {
|
||||
s.Round++
|
||||
s.RoundProfit = fixedpoint.Zero
|
||||
s.RoundFee = make(map[string]fixedpoint.Value)
|
||||
}
|
||||
|
||||
func (s *ProfitStats) CalculateProfitOfRound(ctx context.Context, exchange types.Exchange) error {
|
||||
historyService, ok := exchange.(types.ExchangeTradeHistoryService)
|
||||
if !ok {
|
||||
return fmt.Errorf("exchange %s doesn't support ExchangeTradeHistoryService", exchange.Name())
|
||||
}
|
||||
|
||||
queryService, ok := exchange.(types.ExchangeOrderQueryService)
|
||||
if !ok {
|
||||
return fmt.Errorf("exchange %s doesn't support ExchangeOrderQueryService", exchange.Name())
|
||||
}
|
||||
|
||||
// query the orders of this round
|
||||
orders, err := historyService.QueryClosedOrders(ctx, s.Symbol, time.Time{}, time.Time{}, s.FromOrderID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// query the trades of this round
|
||||
for _, order := range orders {
|
||||
if order.ExecutedQuantity.Sign() == 0 {
|
||||
// skip no trade orders
|
||||
continue
|
||||
}
|
||||
|
||||
trades, err := queryService.QueryOrderTrades(ctx, types.OrderQuery{
|
||||
Symbol: order.Symbol,
|
||||
OrderID: strconv.FormatUint(order.OrderID, 10),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, trade := range trades {
|
||||
s.AddTrade(trade)
|
||||
}
|
||||
}
|
||||
|
||||
s.FromOrderID = s.FromOrderID + 1
|
||||
s.QuoteInvestment = s.QuoteInvestment.Add(s.RoundProfit)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ProfitStats) String() string {
|
||||
var sb strings.Builder
|
||||
sb.WriteString("[------------------ Profit Stats ------------------]\n")
|
||||
sb.WriteString(fmt.Sprintf("Round: %d\n", s.Round))
|
||||
sb.WriteString(fmt.Sprintf("From Order ID: %d\n", s.FromOrderID))
|
||||
sb.WriteString(fmt.Sprintf("Quote Investment: %s\n", s.QuoteInvestment))
|
||||
sb.WriteString(fmt.Sprintf("Round Profit: %s\n", s.RoundProfit))
|
||||
sb.WriteString(fmt.Sprintf("Total Profit: %s\n", s.TotalProfit))
|
||||
for currency, fee := range s.RoundFee {
|
||||
sb.WriteString(fmt.Sprintf("FEE (%s): %s\n", currency, fee))
|
||||
}
|
||||
sb.WriteString("[------------------ Profit Stats ------------------]\n")
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
|
|
@ -35,7 +35,6 @@ func (s *Strategy) recover(ctx context.Context) error {
|
|||
}
|
||||
|
||||
closedOrders, err := queryService.QueryClosedOrdersDesc(ctx, s.Symbol, time.Date(2024, time.January, 1, 0, 0, 0, 0, time.Local), time.Now(), 0)
|
||||
// closedOrders, err := queryService.QueryClosedOrdersDesc(ctx, s.Symbol, time.Time{}, time.Now(), 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -57,16 +56,13 @@ func (s *Strategy) recover(ctx context.Context) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// recover quote investment
|
||||
quoteInvestment := recoverQuoteInvestment(currentRound)
|
||||
// recover profit stats
|
||||
recoverProfitStats(ctx, s.ProfitStats, s.Session.Exchange)
|
||||
|
||||
// recover startTimeOfNextRound
|
||||
startTimeOfNextRound := recoverStartTimeOfNextRound(ctx, currentRound, s.CoolDownInterval)
|
||||
|
||||
s.state = state
|
||||
if !quoteInvestment.IsZero() {
|
||||
s.QuoteInvestment = quoteInvestment
|
||||
}
|
||||
s.startTimeOfNextRound = startTimeOfNextRound
|
||||
|
||||
return nil
|
||||
|
@ -155,7 +151,7 @@ func recoverState(ctx context.Context, symbol string, maxOrderCount int, openOrd
|
|||
|
||||
func recoverPosition(ctx context.Context, position *types.Position, queryService RecoverApiQueryService, currentRound Round) error {
|
||||
if position == nil {
|
||||
return nil
|
||||
return fmt.Errorf("position is nil, please check it")
|
||||
}
|
||||
|
||||
var positionOrders []types.Order
|
||||
|
@ -193,6 +189,16 @@ func recoverPosition(ctx context.Context, position *types.Position, queryService
|
|||
return nil
|
||||
}
|
||||
|
||||
func recoverProfitStats(ctx context.Context, profitStats *ProfitStats, exchange types.Exchange) error {
|
||||
if profitStats == nil {
|
||||
return fmt.Errorf("profit stats is nil, please check it")
|
||||
}
|
||||
|
||||
profitStats.CalculateProfitOfRound(ctx, exchange)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func recoverQuoteInvestment(currentRound Round) fixedpoint.Value {
|
||||
if len(currentRound.OpenPositionOrders) == 0 {
|
||||
return fixedpoint.Zero
|
||||
|
|
|
@ -123,12 +123,19 @@ func (s *Strategy) triggerNextState() {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *Strategy) runWaitToOpenPositionState(_ context.Context, next State) {
|
||||
func (s *Strategy) runWaitToOpenPositionState(ctx context.Context, next State) {
|
||||
s.logger.Info("[State] WaitToOpenPosition - check startTimeOfNextRound")
|
||||
if time.Now().Before(s.startTimeOfNextRound) {
|
||||
return
|
||||
}
|
||||
|
||||
// reset position and open new round for profit stats before position opening
|
||||
s.Position.Reset()
|
||||
s.ProfitStats.NewRound()
|
||||
|
||||
// store into redis
|
||||
bbgo.Sync(ctx, s)
|
||||
|
||||
s.state = PositionOpening
|
||||
s.logger.Info("[State] WaitToOpenPosition -> PositionOpening")
|
||||
}
|
||||
|
@ -184,18 +191,13 @@ func (s *Strategy) runTakeProfitReady(ctx context.Context, next State) {
|
|||
time.Sleep(3 * time.Second)
|
||||
|
||||
s.logger.Info("[State] TakeProfitReady - start reseting position and calculate quote investment for next round")
|
||||
s.QuoteInvestment = s.QuoteInvestment.Add(s.Position.Quote)
|
||||
s.ProfitStats.QuoteInvestment = s.QuoteInvestment
|
||||
|
||||
// reset position
|
||||
s.Position.Reset()
|
||||
|
||||
// reset
|
||||
s.EmitProfit(s.ProfitStats)
|
||||
s.ProfitStats.FinishRound()
|
||||
|
||||
// calculate profit stats
|
||||
s.ProfitStats.CalculateProfitOfRound(ctx, s.Session.Exchange)
|
||||
bbgo.Sync(ctx, s)
|
||||
|
||||
s.EmitProfit(s.ProfitStats)
|
||||
|
||||
// set the start time of the next round
|
||||
s.startTimeOfNextRound = time.Now().Add(s.CoolDownInterval.Duration())
|
||||
s.state = WaitToOpenPosition
|
||||
|
|
|
@ -157,11 +157,6 @@ func (s *Strategy) Run(ctx context.Context, _ bbgo.OrderExecutor, session *bbgo.
|
|||
s.updateTakeProfitPrice()
|
||||
})
|
||||
|
||||
s.OrderExecutor.TradeCollector().OnTrade(func(trade types.Trade, profit, netProfit fixedpoint.Value) {
|
||||
s.ProfitStats.AddTrade(trade)
|
||||
bbgo.Sync(ctx, s)
|
||||
})
|
||||
|
||||
s.OrderExecutor.ActiveMakerOrders().OnFilled(func(o types.Order) {
|
||||
s.logger.Infof("[DCA] FILLED ORDER: %s", o.String())
|
||||
openPositionSide := types.SideTypeBuy
|
||||
|
@ -203,12 +198,10 @@ func (s *Strategy) Run(ctx context.Context, _ bbgo.OrderExecutor, session *bbgo.
|
|||
return
|
||||
}
|
||||
|
||||
s.logger.Infof("[DCA] recovered state: %d", s.state)
|
||||
s.logger.Infof("[DCA] recovered position %s", s.Position.String())
|
||||
s.logger.Infof("[DCA] recovered quote investment %s", s.QuoteInvestment)
|
||||
s.logger.Infof("[DCA] recovered startTimeOfNextRound %s", s.startTimeOfNextRound)
|
||||
|
||||
bbgo.Sync(ctx, s)
|
||||
s.logger.Infof("[DCA] state: %d", s.state)
|
||||
s.logger.Infof("[DCA] position %s", s.Position.String())
|
||||
s.logger.Infof("[DCA] profit stats %s", s.ProfitStats.String())
|
||||
s.logger.Infof("[DCA] startTimeOfNextRound %s", s.startTimeOfNextRound)
|
||||
} else {
|
||||
s.state = WaitToOpenPosition
|
||||
}
|
||||
|
@ -233,8 +226,8 @@ func (s *Strategy) Run(ctx context.Context, _ bbgo.OrderExecutor, session *bbgo.
|
|||
}
|
||||
|
||||
balance := balances[s.Market.QuoteCurrency]
|
||||
if balance.Available.Compare(s.QuoteInvestment) < 0 {
|
||||
return fmt.Errorf("the available balance of %s is %s which is less than quote investment setting %s, please check it", s.Market.QuoteCurrency, balance.Available, s.QuoteInvestment)
|
||||
if balance.Available.Compare(s.ProfitStats.QuoteInvestment) < 0 {
|
||||
return fmt.Errorf("the available balance of %s is %s which is less than quote investment setting %s, please check it", s.Market.QuoteCurrency, balance.Available, s.ProfitStats.QuoteInvestment)
|
||||
}
|
||||
|
||||
bbgo.OnShutdown(ctx, func(ctx context.Context, wg *sync.WaitGroup) {
|
||||
|
|
Loading…
Reference in New Issue
Block a user