2023-11-13 08:20:25 +00:00
package dca2
import (
"context"
"fmt"
2023-11-23 08:45:28 +00:00
"math"
"sync"
"time"
2023-11-13 08:20:25 +00:00
2024-03-15 10:41:25 +00:00
"github.com/cenkalti/backoff/v4"
2024-03-14 06:10:14 +00:00
"github.com/pkg/errors"
2024-02-23 08:56:30 +00:00
"github.com/prometheus/client_golang/prometheus"
"github.com/sirupsen/logrus"
"go.uber.org/multierr"
2023-11-13 08:20:25 +00:00
"github.com/c9s/bbgo/pkg/bbgo"
2024-01-17 09:30:37 +00:00
"github.com/c9s/bbgo/pkg/exchange/retry"
2023-11-13 08:20:25 +00:00
"github.com/c9s/bbgo/pkg/fixedpoint"
2024-01-10 06:37:07 +00:00
"github.com/c9s/bbgo/pkg/strategy/common"
2023-11-13 08:20:25 +00:00
"github.com/c9s/bbgo/pkg/types"
2023-11-23 08:45:28 +00:00
"github.com/c9s/bbgo/pkg/util"
2024-02-23 08:56:30 +00:00
"github.com/c9s/bbgo/pkg/util/tradingutil"
2023-11-13 08:20:25 +00:00
)
2024-02-22 08:15:54 +00:00
const (
ID = "dca2"
orderTag = "dca2"
)
2023-11-13 08:20:25 +00:00
2024-02-22 08:15:54 +00:00
var (
log = logrus . WithField ( "strategy" , ID )
baseLabels prometheus . Labels
)
2023-11-13 08:20:25 +00:00
func init ( ) {
bbgo . RegisterStrategy ( ID , & Strategy { } )
}
2024-01-17 09:30:37 +00:00
type advancedOrderCancelApi interface {
CancelAllOrders ( ctx context . Context ) ( [ ] types . Order , error )
CancelOrdersBySymbol ( ctx context . Context , symbol string ) ( [ ] types . Order , error )
CancelOrdersByGroupID ( ctx context . Context , groupID uint32 ) ( [ ] types . Order , error )
}
2024-01-02 06:09:38 +00:00
//go:generate callbackgen -type Strateg
2023-11-13 08:20:25 +00:00
type Strategy struct {
2024-03-18 08:09:22 +00:00
Position * types . Position ` json:"position,omitempty" persistence:"position" `
ProfitStats * ProfitStats ` json:"profitStats,omitempty" persistence:"profit_stats" `
PersistenceTTL types . Duration ` json:"persistenceTTL" `
2023-11-23 08:45:28 +00:00
2024-03-04 12:52:01 +00:00
Environment * bbgo . Environment
ExchangeSession * bbgo . ExchangeSession
OrderExecutor * bbgo . GeneralOrderExecutor
Market types . Market
2023-11-13 08:20:25 +00:00
Symbol string ` json:"symbol" `
// setting
2024-01-02 09:16:47 +00:00
QuoteInvestment fixedpoint . Value ` json:"quoteInvestment" `
MaxOrderCount int64 ` json:"maxOrderCount" `
2023-11-23 08:45:28 +00:00
PriceDeviation fixedpoint . Value ` json:"priceDeviation" `
TakeProfitRatio fixedpoint . Value ` json:"takeProfitRatio" `
CoolDownInterval types . Duration ` json:"coolDownInterval" `
2023-11-13 08:20:25 +00:00
// OrderGroupID is the group ID used for the strategy instance for canceling orders
2024-05-30 09:23:22 +00:00
OrderGroupID uint32 ` json:"orderGroupID" `
DisableOrderGroupIDFilter bool ` json:"disableOrderGroupIDFilter" `
2023-11-13 08:20:25 +00:00
2023-12-27 08:13:34 +00:00
// RecoverWhenStart option is used for recovering dca states
2024-03-15 10:41:25 +00:00
RecoverWhenStart bool ` json:"recoverWhenStart" `
DisableProfitStatsRecover bool ` json:"disableProfitStatsRecover" `
DisablePositionRecover bool ` json:"disablePositionRecover" `
2023-12-27 08:13:34 +00:00
2024-04-01 02:17:37 +00:00
// EnableQuoteInvestmentReallocate set to true, the quote investment will be reallocated when the notional or quantity is under minimum.
EnableQuoteInvestmentReallocate bool ` json:"enableQuoteInvestmentReallocate" `
2023-12-27 08:13:34 +00:00
// KeepOrdersWhenShutdown option is used for keeping the grid orders when shutting down bbgo
KeepOrdersWhenShutdown bool ` json:"keepOrdersWhenShutdown" `
2024-05-22 10:20:18 +00:00
// UniversalCancelAllOrdersWhenClose close all orders even though the orders don't belong to this strategy
UniversalCancelAllOrdersWhenClose bool ` json:"universalCancelAllOrdersWhenClose" `
2024-01-17 09:30:37 +00:00
2023-11-13 08:20:25 +00:00
// log
logger * logrus . Entry
LogFields logrus . Fields ` json:"logFields" `
2023-12-27 08:13:34 +00:00
// PrometheusLabels will be used as the base prometheus labels
PrometheusLabels prometheus . Labels ` json:"prometheusLabels" `
2023-11-13 08:20:25 +00:00
// private field
2023-11-23 08:45:28 +00:00
mu sync . Mutex
2023-11-27 07:55:02 +00:00
nextStateC chan State
state State
2024-04-16 05:37:53 +00:00
collector * Collector
2024-03-18 08:09:22 +00:00
takeProfitPrice fixedpoint . Value
startTimeOfNextRound time . Time
nextRoundPaused bool
2023-12-27 08:13:34 +00:00
// callbacks
2024-01-10 06:37:07 +00:00
common . StatusCallbacks
2024-04-22 03:07:17 +00:00
profitCallbacks [ ] func ( * ProfitStats )
positionUpdateCallbacks [ ] func ( * types . Position )
2023-11-13 08:20:25 +00:00
}
func ( s * Strategy ) ID ( ) string {
return ID
}
func ( s * Strategy ) Validate ( ) error {
2024-01-02 09:16:47 +00:00
if s . MaxOrderCount < 1 {
return fmt . Errorf ( "maxOrderCount can not be < 1" )
2023-11-13 08:20:25 +00:00
}
2023-11-23 08:45:28 +00:00
if s . TakeProfitRatio . Sign ( ) <= 0 {
2023-11-13 08:20:25 +00:00
return fmt . Errorf ( "takeProfitSpread can not be <= 0" )
}
2023-11-23 08:45:28 +00:00
if s . PriceDeviation . Sign ( ) <= 0 {
2023-11-13 08:20:25 +00:00
return fmt . Errorf ( "margin can not be <= 0" )
}
// TODO: validate balance is enough
return nil
}
func ( s * Strategy ) Defaults ( ) error {
if s . LogFields == nil {
s . LogFields = logrus . Fields { }
}
s . LogFields [ "symbol" ] = s . Symbol
s . LogFields [ "strategy" ] = ID
2024-02-22 08:15:54 +00:00
2023-11-13 08:20:25 +00:00
return nil
}
func ( s * Strategy ) Initialize ( ) error {
s . logger = log . WithFields ( s . LogFields )
return nil
}
func ( s * Strategy ) InstanceID ( ) string {
return fmt . Sprintf ( "%s-%s" , ID , s . Symbol )
}
func ( s * Strategy ) Subscribe ( session * bbgo . ExchangeSession ) {
2023-11-23 08:45:28 +00:00
session . Subscribe ( types . KLineChannel , s . Symbol , types . SubscribeOptions { Interval : types . Interval1m } )
2023-11-13 08:20:25 +00:00
}
2024-02-22 08:15:54 +00:00
func ( s * Strategy ) newPrometheusLabels ( ) prometheus . Labels {
labels := prometheus . Labels {
"exchange" : "default" ,
"symbol" : s . Symbol ,
}
2024-03-04 12:52:01 +00:00
if s . ExchangeSession != nil {
labels [ "exchange" ] = s . ExchangeSession . Name
2024-02-22 08:15:54 +00:00
}
if s . PrometheusLabels == nil {
return labels
}
return mergeLabels ( s . PrometheusLabels , labels )
}
2023-11-13 08:20:25 +00:00
func ( s * Strategy ) Run ( ctx context . Context , _ bbgo . OrderExecutor , session * bbgo . ExchangeSession ) error {
2023-11-23 08:45:28 +00:00
instanceID := s . InstanceID ( )
2024-03-04 12:52:01 +00:00
s . ExchangeSession = session
2024-03-18 08:09:22 +00:00
s . logger . Infof ( "persistence ttl: %s" , s . PersistenceTTL . Duration ( ) )
2024-01-23 07:40:03 +00:00
if s . ProfitStats == nil {
2024-01-02 09:16:47 +00:00
s . ProfitStats = newProfitStats ( s . Market , s . QuoteInvestment )
}
2024-01-23 07:40:03 +00:00
if s . Position == nil {
2024-01-02 09:16:47 +00:00
s . Position = types . NewPositionFromMarket ( s . Market )
}
2024-03-18 08:09:22 +00:00
// set ttl for persistence
s . Position . SetTTL ( s . PersistenceTTL . Duration ( ) )
s . ProfitStats . SetTTL ( s . PersistenceTTL . Duration ( ) )
2024-03-19 07:43:40 +00:00
if s . OrderGroupID == 0 {
s . OrderGroupID = util . FNV32 ( instanceID ) % math . MaxInt32
}
2024-04-16 05:37:53 +00:00
// collector
2024-05-30 09:23:22 +00:00
s . collector = NewCollector ( s . logger , s . Symbol , s . OrderGroupID , ! s . DisableOrderGroupIDFilter , s . ExchangeSession . Exchange )
2024-04-16 05:37:53 +00:00
if s . collector == nil {
return fmt . Errorf ( "failed to initialize collector" )
2024-03-15 10:41:25 +00:00
}
2024-02-22 08:15:54 +00:00
// prometheus
if s . PrometheusLabels != nil {
initMetrics ( labelKeys ( s . PrometheusLabels ) )
}
registerMetrics ( )
// prometheus labels
baseLabels = s . newPrometheusLabels ( )
2024-01-02 09:16:47 +00:00
s . Position . Strategy = ID
s . Position . StrategyInstanceID = instanceID
if session . MakerFeeRate . Sign ( ) > 0 || session . TakerFeeRate . Sign ( ) > 0 {
s . Position . SetExchangeFeeRate ( session . ExchangeName , types . ExchangeFee {
MakerFeeRate : session . MakerFeeRate ,
TakerFeeRate : session . TakerFeeRate ,
} )
}
s . OrderExecutor = bbgo . NewGeneralOrderExecutor ( session , s . Symbol , ID , instanceID , s . Position )
2024-05-09 06:09:44 +00:00
s . OrderExecutor . SetMaxRetries ( 50 )
2024-01-02 09:16:47 +00:00
s . OrderExecutor . BindEnvironment ( s . Environment )
s . OrderExecutor . Bind ( )
2023-11-23 08:45:28 +00:00
// order executor
s . OrderExecutor . TradeCollector ( ) . OnPositionUpdate ( func ( position * types . Position ) {
2024-03-12 06:29:36 +00:00
s . logger . Infof ( "POSITION UPDATE: %s" , s . Position . String ( ) )
2023-11-13 08:20:25 +00:00
bbgo . Sync ( ctx , s )
2023-11-23 08:45:28 +00:00
// update take profit price here
2023-11-27 07:55:02 +00:00
s . updateTakeProfitPrice ( )
2024-04-19 08:24:40 +00:00
// emit position update
2024-04-22 03:07:17 +00:00
s . EmitPositionUpdate ( position )
2023-11-27 07:55:02 +00:00
} )
s . OrderExecutor . ActiveMakerOrders ( ) . OnFilled ( func ( o types . Order ) {
2024-03-12 06:29:36 +00:00
s . logger . Infof ( "FILLED ORDER: %s" , o . String ( ) )
2023-11-27 07:55:02 +00:00
openPositionSide := types . SideTypeBuy
takeProfitSide := types . SideTypeSell
switch o . Side {
case openPositionSide :
2023-12-22 07:27:31 +00:00
s . emitNextState ( OpenPositionOrderFilled )
2023-11-27 07:55:02 +00:00
case takeProfitSide :
2023-12-22 07:27:31 +00:00
s . emitNextState ( WaitToOpenPosition )
2023-11-27 07:55:02 +00:00
default :
2024-03-12 06:29:36 +00:00
s . logger . Infof ( "unsupported side (%s) of order: %s" , o . Side , o )
2023-11-27 07:55:02 +00:00
}
2024-02-22 08:15:54 +00:00
2024-03-21 08:18:48 +00:00
openOrders , err := retry . QueryOpenOrdersUntilSuccessful ( ctx , s . ExchangeSession . Exchange , s . Symbol )
if err != nil {
s . logger . WithError ( err ) . Warn ( "failed to query open orders when order filled" )
} else {
// update open orders metrics
metricsNumOfOpenOrders . With ( baseLabels ) . Set ( float64 ( len ( openOrders ) ) )
}
// update active orders metrics
numActiveMakerOrders := s . OrderExecutor . ActiveMakerOrders ( ) . NumOfOrders ( )
metricsNumOfActiveOrders . With ( baseLabels ) . Set ( float64 ( numActiveMakerOrders ) )
if len ( openOrders ) != numActiveMakerOrders {
s . logger . Warnf ( "num of open orders (%d) and active orders (%d) is different when order filled, please check it." , len ( openOrders ) , numActiveMakerOrders )
}
if err == nil && o . Side == openPositionSide && numActiveMakerOrders == 0 && len ( openOrders ) == 0 {
s . emitNextState ( OpenPositionOrdersCancelling )
}
2023-11-23 08:45:28 +00:00
} )
2023-11-13 08:20:25 +00:00
2023-11-23 08:45:28 +00:00
session . MarketDataStream . OnKLine ( func ( kline types . KLine ) {
2024-05-09 06:09:44 +00:00
switch s . state {
case OpenPositionOrderFilled :
if s . takeProfitPrice . IsZero ( ) {
s . logger . Warn ( "take profit price should not be 0 when there is at least one open-position order filled, please check it" )
return
}
compRes := kline . Close . Compare ( s . takeProfitPrice )
// price doesn't hit the take profit price
if compRes < 0 {
return
}
2023-11-27 07:55:02 +00:00
2024-05-09 06:09:44 +00:00
s . emitNextState ( OpenPositionOrdersCancelling )
default :
2023-11-27 07:55:02 +00:00
return
}
2023-11-13 08:20:25 +00:00
} )
2023-11-23 08:45:28 +00:00
session . UserDataStream . OnAuth ( func ( ) {
2024-03-12 06:29:36 +00:00
s . logger . Info ( "user data stream authenticated" )
2023-11-27 07:55:02 +00:00
time . AfterFunc ( 3 * time . Second , func ( ) {
if isInitialize := s . initializeNextStateC ( ) ; ! isInitialize {
2024-01-23 08:41:54 +00:00
// no need to recover when two situation
// 1. recoverWhenStart is false
// 2. dev mode is on and it's not new strategy
2024-05-09 06:09:44 +00:00
if ! s . RecoverWhenStart {
2024-02-22 08:15:54 +00:00
s . updateState ( WaitToOpenPosition )
2024-01-23 08:41:54 +00:00
} else {
2023-12-27 08:13:34 +00:00
// recover
2024-03-12 06:29:36 +00:00
maxTry := 3
for try := 1 ; try <= maxTry ; try ++ {
s . logger . Infof ( "try #%d recover" , try )
err := s . recover ( ctx )
if err == nil {
s . logger . Infof ( "recover successfully at #%d" , try )
break
}
s . logger . WithError ( err ) . Warnf ( "failed to recover at #%d" , try )
if try == 3 {
s . logger . Errorf ( "failed to recover after %d trying, please check it" , maxTry )
return
}
2024-04-29 07:51:58 +00:00
// sleep 10 second to retry the recovery
time . Sleep ( 10 * time . Second )
2023-12-27 08:13:34 +00:00
}
2023-11-27 07:55:02 +00:00
}
2024-03-12 06:29:36 +00:00
s . logger . Infof ( "state: %d" , s . state )
s . logger . Infof ( "position %s" , s . Position . String ( ) )
s . logger . Infof ( "profit stats %s" , s . ProfitStats . String ( ) )
s . logger . Infof ( "startTimeOfNextRound %s" , s . startTimeOfNextRound )
2024-01-18 07:39:56 +00:00
2024-04-22 05:46:28 +00:00
// emit position after recovery
s . OrderExecutor . TradeCollector ( ) . EmitPositionUpdate ( s . Position )
2023-12-22 07:27:31 +00:00
s . updateTakeProfitPrice ( )
2023-11-27 07:55:02 +00:00
// store persistence
bbgo . Sync ( ctx , s )
2024-01-02 06:09:38 +00:00
// ready
s . EmitReady ( )
2024-04-29 07:51:58 +00:00
// start to sync periodically
go s . syncPeriodically ( ctx )
2024-05-13 07:24:26 +00:00
// try to trigger position opening immediately
if s . state == WaitToOpenPosition {
s . emitNextState ( PositionOpening )
}
2023-11-27 07:55:02 +00:00
// start running state machine
s . runState ( ctx )
}
} )
2023-11-23 08:45:28 +00:00
} )
2023-11-13 08:20:25 +00:00
2023-12-27 08:13:34 +00:00
bbgo . OnShutdown ( ctx , func ( ctx context . Context , wg * sync . WaitGroup ) {
defer wg . Done ( )
if s . KeepOrdersWhenShutdown {
s . logger . Infof ( "keepOrdersWhenShutdown is set, will keep the orders on the exchange" )
return
}
if err := s . Close ( ctx ) ; err != nil {
s . logger . WithError ( err ) . Errorf ( "dca2 graceful order cancel error" )
}
} )
2023-11-23 08:45:28 +00:00
return nil
2023-11-13 08:20:25 +00:00
}
2023-11-27 07:55:02 +00:00
func ( s * Strategy ) updateTakeProfitPrice ( ) {
takeProfitRatio := s . TakeProfitRatio
s . takeProfitPrice = s . Market . TruncatePrice ( s . Position . AverageCost . Mul ( fixedpoint . One . Add ( takeProfitRatio ) ) )
2024-03-12 06:29:36 +00:00
s . logger . Infof ( "cost: %s, ratio: %s, price: %s" , s . Position . AverageCost . String ( ) , takeProfitRatio . String ( ) , s . takeProfitPrice . String ( ) )
2023-11-27 07:55:02 +00:00
}
2023-12-27 08:13:34 +00:00
func ( s * Strategy ) Close ( ctx context . Context ) error {
2024-03-12 06:29:36 +00:00
s . logger . Infof ( "closing %s dca2" , s . Symbol )
2023-12-27 08:13:34 +00:00
defer s . EmitClosed ( )
2024-05-22 10:20:18 +00:00
var err error
if s . UniversalCancelAllOrdersWhenClose {
err = tradingutil . UniversalCancelAllOrders ( ctx , s . ExchangeSession . Exchange , nil )
} else {
err = s . OrderExecutor . GracefulCancel ( ctx )
}
2024-01-03 08:41:59 +00:00
if err != nil {
2024-05-22 10:20:18 +00:00
s . logger . WithError ( err ) . Errorf ( "there are errors when cancelling orders when closing (UniversalCancelAllOrdersWhenClose = %t)" , s . UniversalCancelAllOrdersWhenClose )
2024-01-03 08:41:59 +00:00
}
2023-12-27 08:13:34 +00:00
2024-01-03 08:41:59 +00:00
bbgo . Sync ( ctx , s )
return err
2023-12-27 08:13:34 +00:00
}
func ( s * Strategy ) CleanUp ( ctx context . Context ) error {
_ = s . Initialize ( )
defer s . EmitClosed ( )
2024-01-03 08:41:59 +00:00
2024-03-04 12:52:01 +00:00
session := s . ExchangeSession
2024-01-17 09:30:37 +00:00
if session == nil {
return fmt . Errorf ( "Session is nil, please check it" )
2024-01-03 08:41:59 +00:00
}
2024-02-23 08:56:30 +00:00
// ignore the first cancel error, this skips one open-orders query request
if err := tradingutil . UniversalCancelAllOrders ( ctx , session . Exchange , nil ) ; err == nil {
return nil
2024-01-17 09:30:37 +00:00
}
2024-02-23 08:56:30 +00:00
// if cancel all orders returns error, get the open orders and retry the cancel in each round
2024-01-17 09:30:37 +00:00
var werr error
for {
s . logger . Infof ( "checking %s open orders..." , s . Symbol )
openOrders , err := retry . QueryOpenOrdersUntilSuccessful ( ctx , session . Exchange , s . Symbol )
if err != nil {
2024-02-23 08:56:30 +00:00
s . logger . WithError ( err ) . Errorf ( "unable to query open orders" )
continue
2024-01-17 09:30:37 +00:00
}
2024-02-23 08:56:30 +00:00
// all clean up
2024-01-17 09:30:37 +00:00
if len ( openOrders ) == 0 {
break
}
2024-02-23 08:56:30 +00:00
if err := tradingutil . UniversalCancelAllOrders ( ctx , session . Exchange , openOrders ) ; err != nil {
s . logger . WithError ( err ) . Errorf ( "unable to cancel all orders" )
2024-01-17 09:30:37 +00:00
werr = multierr . Append ( werr , err )
}
time . Sleep ( 1 * time . Second )
}
return werr
2023-12-27 08:13:34 +00:00
}
2024-01-09 08:01:10 +00:00
2024-03-18 08:09:22 +00:00
// PauseNextRound will stop openning open-position orders at the next round
func ( s * Strategy ) PauseNextRound ( ) {
s . nextRoundPaused = true
}
func ( s * Strategy ) ContinueNextRound ( ) {
s . nextRoundPaused = false
}
2024-03-15 10:41:25 +00:00
func ( s * Strategy ) UpdateProfitStatsUntilSuccessful ( ctx context . Context ) error {
2024-03-14 06:10:14 +00:00
var op = func ( ) error {
2024-03-15 10:41:25 +00:00
if updated , err := s . UpdateProfitStats ( ctx ) ; err != nil {
return errors . Wrapf ( err , "failed to update profit stats, please check it" )
} else if ! updated {
return fmt . Errorf ( "there is no round to update profit stats, please check it" )
2024-03-14 03:40:03 +00:00
}
2024-03-14 06:10:14 +00:00
return nil
2024-03-14 03:40:03 +00:00
}
2024-03-15 10:41:25 +00:00
// exponential increased interval retry until success
bo := backoff . NewExponentialBackOff ( )
bo . InitialInterval = 5 * time . Second
bo . MaxInterval = 20 * time . Minute
bo . MaxElapsedTime = 0
return backoff . Retry ( op , backoff . WithContext ( bo , ctx ) )
2024-03-14 03:40:03 +00:00
}
2024-03-15 10:41:25 +00:00
// UpdateProfitStats will collect round from closed orders and emit update profit stats
// return true, nil -> there is at least one finished round and all the finished rounds we collect update profit stats successfully
// return false, nil -> there is no finished round!
// return true, error -> At least one round update profit stats successfully but there is error when collecting other rounds
func ( s * Strategy ) UpdateProfitStats ( ctx context . Context ) ( bool , error ) {
2024-04-29 07:51:58 +00:00
s . logger . Info ( "update profit stats" )
2024-04-16 05:37:53 +00:00
rounds , err := s . collector . CollectFinishRounds ( ctx , s . ProfitStats . FromOrderID )
2024-01-09 08:01:10 +00:00
if err != nil {
2024-03-15 10:41:25 +00:00
return false , errors . Wrapf ( err , "failed to collect finish rounds from #%d" , s . ProfitStats . FromOrderID )
2024-01-09 08:01:10 +00:00
}
2024-01-10 06:37:07 +00:00
2024-03-15 10:41:25 +00:00
var updated bool = false
2024-01-12 16:24:47 +00:00
for _ , round := range rounds {
2024-04-16 05:37:53 +00:00
trades , err := s . collector . CollectRoundTrades ( ctx , round )
2024-03-15 10:41:25 +00:00
if err != nil {
return updated , errors . Wrapf ( err , "failed to collect the trades of round" )
}
2024-01-09 08:01:10 +00:00
2024-03-15 10:41:25 +00:00
for _ , trade := range trades {
s . logger . Infof ( "update profit stats from trade: %s" , trade . String ( ) )
s . ProfitStats . AddTrade ( trade )
2024-01-09 08:01:10 +00:00
}
2024-03-15 10:41:25 +00:00
// update profit stats FromOrderID to make sure we will not collect duplicated rounds
2024-05-29 08:32:18 +00:00
for _ , order := range round . TakeProfitOrders {
if order . OrderID >= s . ProfitStats . FromOrderID {
s . ProfitStats . FromOrderID = order . OrderID + 1
}
}
2024-03-15 10:41:25 +00:00
// update quote investment
2024-01-12 16:24:47 +00:00
s . ProfitStats . QuoteInvestment = s . ProfitStats . QuoteInvestment . Add ( s . ProfitStats . CurrentRoundProfit )
2024-03-15 10:41:25 +00:00
// sync to persistence
2024-01-12 16:24:47 +00:00
bbgo . Sync ( ctx , s )
2024-03-15 10:41:25 +00:00
updated = true
2024-01-12 16:24:47 +00:00
2024-03-12 06:29:36 +00:00
s . logger . Infof ( "profit stats:\n%s" , s . ProfitStats . String ( ) )
2024-01-23 08:41:54 +00:00
2024-01-12 16:24:47 +00:00
// emit profit
s . EmitProfit ( s . ProfitStats )
2024-02-22 08:15:54 +00:00
updateProfitMetrics ( s . ProfitStats . Round , s . ProfitStats . CurrentRoundProfit . Float64 ( ) )
2024-01-12 16:24:47 +00:00
2024-03-15 10:41:25 +00:00
// make profit stats forward to new round
2024-01-12 16:24:47 +00:00
s . ProfitStats . NewRound ( )
}
2024-01-09 08:01:10 +00:00
2024-03-15 10:41:25 +00:00
return updated , nil
2024-01-09 08:01:10 +00:00
}