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
"github.com/c9s/bbgo/pkg/bbgo"
"github.com/c9s/bbgo/pkg/fixedpoint"
2023-11-23 08:45:28 +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"
2023-11-13 08:20:25 +00:00
"github.com/sirupsen/logrus"
)
const ID = "dca2"
const orderTag = "dca2"
var log = logrus . WithField ( "strategy" , ID )
func init ( ) {
bbgo . RegisterStrategy ( ID , & Strategy { } )
}
type Strategy struct {
2023-11-23 08:45:28 +00:00
* common . Strategy
2023-11-13 08:20:25 +00:00
Environment * bbgo . Environment
Market types . Market
Symbol string ` json:"symbol" `
// setting
2023-11-23 08:45:28 +00:00
Short bool ` json:"short" `
2023-11-13 08:20:25 +00:00
Budget fixedpoint . Value ` json:"budget" `
2023-11-23 08:45:28 +00:00
MaxOrderNum int64 ` json:"maxOrderNum" `
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
OrderGroupID uint32 ` json:"orderGroupID" `
// log
logger * logrus . Entry
LogFields logrus . Fields ` json:"logFields" `
// private field
2023-11-23 08:45:28 +00:00
mu sync . Mutex
2023-12-07 06:38:13 +00:00
openPositionSide types . SideType
2023-11-23 08:45:28 +00:00
takeProfitSide types . SideType
takeProfitPrice fixedpoint . Value
startTimeOfNextRound time . Time
2023-11-13 08:20:25 +00:00
}
func ( s * Strategy ) ID ( ) string {
return ID
}
func ( s * Strategy ) Validate ( ) error {
2023-11-23 08:45:28 +00:00
if s . MaxOrderNum < 1 {
2023-11-13 08:20:25 +00:00
return fmt . Errorf ( "maxOrderNum can not be < 1" )
}
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
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
}
func ( s * Strategy ) Run ( ctx context . Context , _ bbgo . OrderExecutor , session * bbgo . ExchangeSession ) error {
2023-11-23 08:45:28 +00:00
s . Strategy = & common . Strategy { }
s . Strategy . Initialize ( ctx , s . Environment , session , s . Market , ID , s . InstanceID ( ) )
instanceID := s . InstanceID ( )
if s . Short {
2023-12-07 06:38:13 +00:00
s . openPositionSide = types . SideTypeSell
2023-11-23 08:45:28 +00:00
s . takeProfitSide = types . SideTypeBuy
} else {
2023-12-07 06:38:13 +00:00
s . openPositionSide = types . SideTypeBuy
2023-11-23 08:45:28 +00:00
s . takeProfitSide = types . SideTypeSell
2023-11-13 08:20:25 +00:00
}
2023-11-23 08:45:28 +00:00
if s . OrderGroupID == 0 {
s . OrderGroupID = util . FNV32 ( instanceID ) % math . MaxInt32
2023-11-13 08:20:25 +00:00
}
2023-11-23 08:45:28 +00:00
// order executor
s . OrderExecutor . TradeCollector ( ) . OnPositionUpdate ( func ( position * types . Position ) {
s . logger . Infof ( "position: %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-13 08:20:25 +00:00
2023-11-23 08:45:28 +00:00
session . MarketDataStream . OnKLine ( func ( kline types . KLine ) {
// check price here
2023-11-13 08:20:25 +00:00
} )
2023-11-23 08:45:28 +00:00
session . UserDataStream . OnAuth ( func ( ) {
s . logger . Info ( "user data stream authenticated, start the process" )
// decide state here
} )
2023-11-13 08:20:25 +00:00
2023-11-23 08:45:28 +00:00
balances , err := session . Exchange . QueryAccountBalances ( ctx )
if err != nil {
return err
2023-11-13 08:20:25 +00:00
}
2023-11-23 08:45:28 +00:00
balance := balances [ s . Market . QuoteCurrency ]
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 )
2023-11-13 08:20:25 +00:00
}
2023-11-23 08:45:28 +00:00
return nil
2023-11-13 08:20:25 +00:00
}