qbtrade/pkg/strategy/newTest/strategy.go
2024-07-21 22:42:15 +08:00

471 lines
15 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package newTest
import (
"context"
"fmt"
"git.qtrade.icu/lychiyu/qbtrade/pkg/fixedpoint"
"git.qtrade.icu/lychiyu/qbtrade/pkg/qbtrade"
"git.qtrade.icu/lychiyu/qbtrade/pkg/strategy/common"
"git.qtrade.icu/lychiyu/qbtrade/pkg/types"
"git.qtrade.icu/lychiyu/qbtrade/pkg/util"
log "github.com/sirupsen/logrus"
"strings"
"sync"
)
const ID = "new_test"
const (
ShortTag = "short"
ShortProfitTag = "short_profit"
ShortLossTag = "short_loss"
LongTag = "long"
LongProfitTag = "long_profit"
LongLossTag = "long_loss"
)
func init() {
qbtrade.RegisterStrategy(ID, &Strategy{})
}
type Strategy struct {
*common.Strategy
Environment *qbtrade.Environment
markets map[string]types.Market
// persistence fields
Positions map[string]*types.Position `persistence:"position"`
ProfitStats *types.ProfitStats `persistence:"profit_stats"`
//TradeStats *types.TradeStats `persistence:"trade_stats"`
//配置文件
ReCalculate bool `json:"recalculate"`
OrderType types.OrderType `json:"orderType"`
Symbols []string `json:"symbols"`
Interval types.Interval `json:"interval"`
NRCount int `json:"nr_count"`
DryRun bool `json:"dry_run"`
ProfitRange fixedpoint.Value `json:"profitRange"`
LossRange fixedpoint.Value `json:"lossRange"`
StrictMode bool `json:"strict_mode"`
Leverage fixedpoint.Value `json:"leverage"`
qbtrade.QuantityOrAmount
// 计算NR的历史kline
CalKLines map[string][]types.KLine
// 符合NR的kline
LastNRCandles map[string]*types.KLine
session *qbtrade.ExchangeSession
orderExecutors map[string]*qbtrade.GeneralOrderExecutor
//AccountValueCalculator *qbtrade.AccountValueCalculator
qbtrade.StrategyController
ordered map[string]bool // 是否已经下单
orderedSide map[string]string // 成交单的方向
LongOrder map[string]types.SubmitOrder
LongProfitOrder map[string]types.SubmitOrder
LongLossOrder map[string]types.SubmitOrder
ShortOrder map[string]types.SubmitOrder
ShortProfitOrder map[string]types.SubmitOrder
ShortLossOrder map[string]types.SubmitOrder
}
func (s *Strategy) Defaults() error {
if s.OrderType == "" {
log.Infof("order type is not set, using limit maker order type")
s.OrderType = types.OrderTypeLimit
//s.OrderType = types.OrderTypeStopLimit
}
return nil
}
func (s *Strategy) Initialize() error {
if s.Strategy == nil {
s.Strategy = &common.Strategy{}
}
return nil
}
func (s *Strategy) ID() string {
return ID
}
func (s *Strategy) InstanceID() string {
return fmt.Sprintf("%s:%s:%s", ID, strings.Join(s.Symbols, "-"), s.Interval)
}
func (s *Strategy) Subscribe(session *qbtrade.ExchangeSession) {
for _, symbol := range s.Symbols {
session.Subscribe(types.KLineChannel, symbol, types.SubscribeOptions{Interval: s.Interval})
session.Subscribe(types.MarketTradeChannel, symbol, types.SubscribeOptions{})
}
}
func (s *Strategy) OnKLineClosed(ctx context.Context, kline types.KLine, symbol string) {
if s.ordered[symbol] {
return
}
calKLines := s.CalKLines[symbol]
if len(s.CalKLines) < s.NRCount {
return
}
nr := calKLines[len(calKLines)-1]
preNr := calKLines[len(calKLines)-2]
isNR := true
if preNr.High < nr.High || preNr.Low > nr.Low {
isNR = false
return
}
for i := len(calKLines) - s.NRCount; i < len(calKLines); i++ {
// 这种是所有的kline都要高于nr
//if s.CalKLines[i].High > nr.High || s.CalKLines[i].Low < nr.Low {
// isNR = false
// break
//}
if s.StrictMode {
if calKLines[i].High-calKLines[i].Low < nr.High-nr.Low {
isNR = false
break
}
} else {
if (calKLines[i].High-calKLines[i].Low)/calKLines[i].Low < (nr.High-nr.Low)/nr.Low {
isNR = false
break
}
}
}
if isNR {
s.LastNRCandles[symbol] = &nr
log.Infof("交易信号(%s)%+v", symbol, kline)
s.placeOrders(ctx, symbol)
return
}
}
func (s *Strategy) cancelSideOrder(ctx context.Context, symbol string) {
if s.orderedSide[symbol] == "" || len(s.orderExecutors[symbol].ActiveMakerOrders().Orders()) <= 0 {
return
}
if s.orderedSide[symbol] == LongTag {
log.Infof("the long order is filled (%s), will cancel short order", symbol)
s.orderExecutors[symbol].CancelOrders(ctx, types.Order{SubmitOrder: s.ShortOrder[symbol]})
s.orderExecutors[symbol].CancelOrders(ctx, types.Order{SubmitOrder: s.ShortLossOrder[symbol]})
s.orderExecutors[symbol].CancelOrders(ctx, types.Order{SubmitOrder: s.ShortProfitOrder[symbol]})
} else {
log.Infof("the short order is filled (%s), will cancel short order", symbol)
s.orderExecutors[symbol].CancelOrders(ctx, types.Order{SubmitOrder: s.LongOrder[symbol]})
s.orderExecutors[symbol].CancelOrders(ctx, types.Order{SubmitOrder: s.LongLossOrder[symbol]})
s.orderExecutors[symbol].CancelOrders(ctx, types.Order{SubmitOrder: s.LongProfitOrder[symbol]})
}
}
func (s *Strategy) Run(ctx context.Context, orderExecutor qbtrade.OrderExecutor, session *qbtrade.ExchangeSession) error {
s.session = session
s.markets = s.session.Markets()
s.Positions = make(map[string]*types.Position)
s.CalKLines = make(map[string][]types.KLine)
s.LastNRCandles = make(map[string]*types.KLine)
s.orderExecutors = make(map[string]*qbtrade.GeneralOrderExecutor)
s.ordered = make(map[string]bool)
s.orderedSide = make(map[string]string)
s.LongOrder = make(map[string]types.SubmitOrder)
s.LongLossOrder = make(map[string]types.SubmitOrder)
s.LongProfitOrder = make(map[string]types.SubmitOrder)
s.ShortOrder = make(map[string]types.SubmitOrder)
s.ShortLossOrder = make(map[string]types.SubmitOrder)
s.ShortProfitOrder = make(map[string]types.SubmitOrder)
qbtrade.Notify("NR4策略开始执行...")
//for _, symbol := range s.Symbols {
// s.Strategy.Initialize(ctx, s.Environment, session, s.markets[symbol], ID, s.InstanceID())
//}
//
for _, symbol := range s.Symbols {
s.Positions[symbol] = types.NewPositionFromMarket(s.markets[symbol])
}
//
//if s.ProfitStats == nil {
// s.ProfitStats = types.NewProfitStats(s.Market)
//}
//if s.TradeStats == nil {
// s.TradeStats = types.NewTradeStats(s.Symbol)
//}
s.OnSuspend(func() {
// Cancel active orders
for _, symbol := range s.Symbols {
_ = s.orderExecutors[symbol].GracefulCancel(ctx)
}
})
s.OnEmergencyStop(func() {
// Cancel active orders
for _, symbol := range s.Symbols {
_ = s.orderExecutors[symbol].GracefulCancel(ctx)
}
// Close 100% position
//_ = s.ClosePosition(ctx, fixedpoint.One)
})
for _, symbol := range s.Symbols {
s.orderExecutors[symbol] = qbtrade.NewGeneralOrderExecutor(session, symbol, ID, s.InstanceID(), s.Positions[symbol])
s.orderExecutors[symbol].BindEnvironment(s.Environment)
_ = s.orderExecutors[symbol].GracefulCancel(ctx)
//s.orderExecutors[symbol].BindProfitStats(s.ProfitStats)
//s.orderExecutor.BindTradeStats(s.TradeStats)
//s.orderExecutors[symbol].TradeCollector().OnPositionUpdate(func(position *types.Position) {
// log.Infof("position is updated, symbol (%s): %+v", symbol, position)
//})
}
// AccountValueCalculator
//s.AccountValueCalculator = qbtrade.NewAccountValueCalculator(s.session, s.Market.QuoteCurrency)
s.session.MarketDataStream.OnKLineClosed(func(kline types.KLine) {
for _, symbol := range s.Symbols {
if kline.Symbol != symbol {
continue
}
if !s.ordered[symbol] {
// 在下一根k线时没有成交订单则取消所有订单
s.cancelOrders(ctx, symbol)
} else {
// 如果有订单则不再进行NR的计算
return
}
s.CalKLines[symbol] = []types.KLine{}
// 获取历史最近的4根K线
if !s.ReCalculate {
lines, err := s.session.Exchange.QueryKLines(ctx, symbol, s.Interval, types.KLineQueryOptions{Limit: s.NRCount})
s.CalKLines[symbol] = lines
if err != nil {
util.LogErr(err, fmt.Sprintf("failed to close position %s", symbol))
}
}
if len(s.CalKLines) < s.NRCount {
s.CalKLines[symbol] = append(s.CalKLines[symbol], kline)
} else {
s.OnKLineClosed(ctx, kline, symbol)
}
}
})
// 监听市场的交易事件
//session.MarketDataStream.OnMarketTrade(func(trade types.Trade) {
// // handle market trade event here
// fmt.Println(trade)
//})
session.UserDataStream.OnOrderUpdate(func(order types.Order) {
orderSymbol := order.Symbol
log.Infof("the order is: %+v,id is %d type is %s, status is %s", order, order.OrderID, order.Type, order.Status)
s.cancelSideOrder(ctx, orderSymbol)
if order.Status == types.OrderStatusFilled {
if order.Type == types.OrderTypeLimit && order.Side == types.SideTypeBuy {
log.Infof("the long order is filled: %+v,id is %d, symbol is %s, type is %s, status is %s",
order, order.OrderID, orderSymbol, order.Type, order.Status)
s.ordered[orderSymbol] = true
s.orderedSide[orderSymbol] = LongTag
qbtrade.Notify("订单成交通知:\n 币种:%s, 方向:%s, 价格:%s, 数量:%s", order.Symbol, LongTag, order.Price, order.Quantity)
}
if order.Type == types.OrderTypeLimit && order.Side == types.SideTypeSell {
log.Infof("the short order is filled: %+v,id is %d, symbol is %s, type is %s, status is %s",
order, order.OrderID, orderSymbol, order.Type, order.Status)
s.ordered[orderSymbol] = true
s.orderedSide[orderSymbol] = ShortTag
qbtrade.Notify("订单成交通知:\n 币种:%s, 方向:%s, 价格:%s, 数量:%s", order.Symbol, ShortTag, order.Price, order.Quantity)
}
if order.Type == types.OrderTypeMarket {
log.Infof("the loss or profit order is filled: %+v,id is %d, symbol is %s, type is %s, status is %s",
order, order.OrderID, orderSymbol, order.Type, order.Status)
qbtrade.Notify("订单止盈或止损通知:\n %s:", order.Symbol, order.Price)
s.ordered[orderSymbol] = false
}
} else {
log.Infof("the order is: %+v,id is %d, symbol is %s, type is %s, status is %s",
order, order.OrderID, orderSymbol, order.Type, order.Status)
}
})
session.UserDataStream.OnTradeUpdate(func(trade types.Trade) {
log.Infof("trade price %f, fee %f %s", trade.Price.Float64(), trade.Fee.Float64(), trade.FeeCurrency)
})
session.UserDataStream.OnBalanceUpdate(func(balances types.BalanceMap) {
log.Infof("balance update: %+v", balances)
})
qbtrade.OnShutdown(ctx, func(ctx context.Context, wg *sync.WaitGroup) {
defer wg.Done()
qbtrade.Sync(ctx, s)
})
return nil
}
func (s *Strategy) cancelOrders(ctx context.Context, symbol string) {
if len(s.orderExecutors[symbol].ActiveMakerOrders().Orders()) <= 0 {
return
}
log.Infof("the order is not filled, will cancel all orders")
if err := s.orderExecutors[symbol].GracefulCancel(ctx); err != nil {
log.WithError(err).Errorf("failed to cancel orders")
}
}
func (s *Strategy) placeOrders(ctx context.Context, symbol string) {
orders, err := s.generateOrders(ctx, symbol)
if err != nil {
log.WithError(err).Error(fmt.Sprintf("failed to generate orders (%s)", symbol))
return
}
log.Infof("orders: %+v", orders)
if s.DryRun {
log.Infof("dry run, not submitting orders (%s)", symbol)
return
}
createdOrders, err := s.orderExecutors[symbol].SubmitOrders(ctx, orders...)
if err != nil {
log.WithError(err).Error(fmt.Sprintf("failed to submit orders (%s)", symbol))
return
}
log.Infof("created orders (%s): %+v", symbol, createdOrders)
}
func (s *Strategy) generateOrders(ctx context.Context, symbol string) ([]types.SubmitOrder, error) {
var orders []types.SubmitOrder
// 卖价
sellPrice := fixedpoint.NewFromFloat(s.LastNRCandles[symbol].High.Float64())
// 买价
buyPrice := fixedpoint.NewFromFloat(s.LastNRCandles[symbol].Low.Float64())
buyQuantity := s.QuantityOrAmount.CalculateQuantity(buyPrice).Mul(s.Leverage)
sellQuantity := s.QuantityOrAmount.CalculateQuantity(sellPrice).Mul(s.Leverage)
log.Infof("generateOrders (%s), sellPrice is %s, sellQuantity is %s, "+
"buyPrice is %s, buyQuantity is %s", symbol, sellPrice, sellQuantity, buyPrice, buyQuantity)
s.ShortOrder[symbol] = types.SubmitOrder{
Symbol: symbol,
Side: types.SideTypeSell,
Type: s.OrderType,
Price: sellPrice,
PositionSide: types.PositionSideTypeShort,
Quantity: sellQuantity,
TimeInForce: types.TimeInForceGTC,
Market: s.markets[symbol],
Tag: ShortTag,
}
s.ShortProfitOrder[symbol] = types.SubmitOrder{
Symbol: symbol,
Side: types.SideTypeBuy,
Type: types.OrderTypeTakeProfitMarket,
PositionSide: types.PositionSideTypeShort,
StopPrice: sellPrice.Sub(sellPrice.Mul(s.ProfitRange)),
TimeInForce: types.TimeInForceGTC,
Market: s.markets[symbol],
Tag: ShortProfitTag,
ClosePosition: true,
}
s.ShortLossOrder[symbol] = types.SubmitOrder{
Symbol: symbol,
Side: types.SideTypeBuy,
Type: types.OrderTypeStopMarket,
PositionSide: types.PositionSideTypeShort,
StopPrice: buyPrice.Add(sellPrice.Mul(s.LossRange)),
TimeInForce: types.TimeInForceGTC,
Market: s.markets[symbol],
Tag: ShortLossTag,
ClosePosition: true,
}
s.LongOrder[symbol] = types.SubmitOrder{
Symbol: symbol,
Side: types.SideTypeBuy,
Type: s.OrderType,
Price: buyPrice,
PositionSide: types.PositionSideTypeLong,
Quantity: buyQuantity,
TimeInForce: types.TimeInForceGTC,
Market: s.markets[symbol],
Tag: LongTag,
}
s.LongProfitOrder[symbol] = types.SubmitOrder{
Symbol: symbol,
Side: types.SideTypeSell,
Type: types.OrderTypeTakeProfitMarket,
PositionSide: types.PositionSideTypeLong,
StopPrice: buyPrice.Add(buyPrice.Mul(s.ProfitRange)),
TimeInForce: types.TimeInForceGTC,
Market: s.markets[symbol],
Tag: LongProfitTag,
ClosePosition: true,
}
s.LongLossOrder[symbol] = types.SubmitOrder{
Symbol: symbol,
Side: types.SideTypeSell,
Type: types.OrderTypeStopMarket,
PositionSide: types.PositionSideTypeLong,
StopPrice: sellPrice.Sub(buyPrice.Mul(s.LossRange)),
TimeInForce: types.TimeInForceGTC,
Market: s.markets[symbol],
Tag: LongLossTag,
ClosePosition: true,
}
//// 挂空单
//orders = append(orders, s.ShortOrder[symbol])
//// 挂多单
//orders = append(orders, s.LongOrder[symbol])
//
//// 空单止盈
//orders = append(orders, s.ShortProfitOrder[symbol])
//// 空单止损
//orders = append(orders, s.ShortLossOrder[symbol])
//
//// 多单止盈
//orders = append(orders, s.LongProfitOrder[symbol])
//// 多单止损
//orders = append(orders, s.LongLossOrder[symbol])
if s.LastNRCandles[symbol].Open > s.LastNRCandles[symbol].Close {
// 挂空单
orders = append(orders, s.ShortOrder[symbol])
// 空单止盈
orders = append(orders, s.ShortProfitOrder[symbol])
// 空单止损
orders = append(orders, s.ShortLossOrder[symbol])
}
if s.LastNRCandles[symbol].Open < s.LastNRCandles[symbol].Close {
// 挂多单
orders = append(orders, s.LongOrder[symbol])
// 多单止盈
orders = append(orders, s.LongProfitOrder[symbol])
// 多单止损
orders = append(orders, s.LongLossOrder[symbol])
}
return orders, nil
}