2022-06-18 08:31:53 +00:00
package bbgo
import (
"context"
2022-07-04 12:13:54 +00:00
"fmt"
2022-06-27 10:17:57 +00:00
"strings"
2022-09-16 04:19:30 +00:00
"time"
2022-06-18 08:31:53 +00:00
2022-11-02 04:31:35 +00:00
"github.com/pkg/errors"
2022-06-18 08:31:53 +00:00
log "github.com/sirupsen/logrus"
2022-09-09 10:41:06 +00:00
"go.uber.org/multierr"
2022-06-18 08:31:53 +00:00
2023-07-04 13:42:24 +00:00
"github.com/c9s/bbgo/pkg/core"
2023-06-29 02:56:07 +00:00
"github.com/c9s/bbgo/pkg/exchange/retry"
2022-06-18 08:31:53 +00:00
"github.com/c9s/bbgo/pkg/fixedpoint"
"github.com/c9s/bbgo/pkg/types"
2022-09-16 04:19:30 +00:00
"github.com/c9s/bbgo/pkg/util"
2023-07-20 04:44:57 +00:00
"github.com/c9s/bbgo/pkg/util/backoff"
2022-06-18 08:31:53 +00:00
)
2022-09-23 17:25:28 +00:00
var ErrExceededSubmitOrderRetryLimit = errors . New ( "exceeded submit order retry limit" )
// quantityReduceDelta is used to modify the order to submit, especially for the market order
var quantityReduceDelta = fixedpoint . NewFromFloat ( 0.005 )
// submitOrderRetryLimit is used when SubmitOrder failed, we will re-submit the order.
// This is for the maximum retries
const submitOrderRetryLimit = 5
2024-08-18 05:16:55 +00:00
// BaseOrderExecutor provides the common accessors for order executor
2023-08-04 10:02:24 +00:00
type BaseOrderExecutor struct {
2024-08-18 05:16:55 +00:00
exchange types . Exchange
2023-08-04 10:02:24 +00:00
session * ExchangeSession
activeMakerOrders * ActiveOrderBook
orderStore * core . OrderStore
}
2023-08-04 17:59:52 +00:00
func ( e * BaseOrderExecutor ) OrderStore ( ) * core . OrderStore {
return e . orderStore
}
func ( e * BaseOrderExecutor ) ActiveMakerOrders ( ) * ActiveOrderBook {
return e . activeMakerOrders
}
2023-08-04 18:15:16 +00:00
// GracefulCancel cancels all active maker orders if orders are not given, otherwise cancel all the given orders
func ( e * BaseOrderExecutor ) GracefulCancel ( ctx context . Context , orders ... types . Order ) error {
2024-08-18 05:16:55 +00:00
if err := e . activeMakerOrders . GracefulCancel ( ctx , e . exchange , orders ... ) ; err != nil {
return errors . Wrap ( err , "graceful cancel order error" )
2023-08-04 18:15:16 +00:00
}
return nil
}
2022-06-18 08:31:53 +00:00
// GeneralOrderExecutor implements the general order executor for strategy
type GeneralOrderExecutor struct {
2023-08-04 10:02:24 +00:00
BaseOrderExecutor
2022-06-18 08:31:53 +00:00
symbol string
strategy string
strategyInstanceID string
position * types . Position
2023-07-05 07:26:36 +00:00
tradeCollector * core . TradeCollector
2022-09-16 04:19:30 +00:00
2023-03-02 08:16:14 +00:00
logger log . FieldLogger
2022-09-16 04:19:30 +00:00
marginBaseMaxBorrowable , marginQuoteMaxBorrowable fixedpoint . Value
2022-09-19 05:12:49 +00:00
2023-03-23 04:51:52 +00:00
maxRetries uint
2022-11-16 09:10:58 +00:00
disableNotify bool
2022-06-18 08:31:53 +00:00
}
2023-11-28 07:54:06 +00:00
// NewGeneralOrderExecutor allocates a GeneralOrderExecutor
// which has its own order store, trade collector
2023-09-26 12:42:38 +00:00
func NewGeneralOrderExecutor (
2023-11-28 07:54:06 +00:00
session * ExchangeSession ,
symbol , strategy , strategyInstanceID string ,
position * types . Position ,
2023-09-26 12:42:38 +00:00
) * GeneralOrderExecutor {
2022-06-18 08:31:53 +00:00
// Always update the position fields
position . Strategy = strategy
position . StrategyInstanceID = strategyInstanceID
2023-07-04 13:42:24 +00:00
orderStore := core . NewOrderStore ( symbol )
2022-09-16 04:19:30 +00:00
executor := & GeneralOrderExecutor {
2023-08-04 10:02:24 +00:00
BaseOrderExecutor : BaseOrderExecutor {
session : session ,
2024-08-17 10:00:49 +00:00
exchange : session . Exchange ,
2023-08-04 10:02:24 +00:00
activeMakerOrders : NewActiveOrderBook ( symbol ) ,
orderStore : orderStore ,
} ,
2022-06-18 08:31:53 +00:00
symbol : symbol ,
strategy : strategy ,
strategyInstanceID : strategyInstanceID ,
position : position ,
2023-07-05 07:26:36 +00:00
tradeCollector : core . NewTradeCollector ( symbol , position , orderStore ) ,
2022-06-18 08:31:53 +00:00
}
2022-09-16 04:19:30 +00:00
2023-07-04 13:31:47 +00:00
if session != nil && session . Margin {
2022-09-16 04:19:30 +00:00
executor . startMarginAssetUpdater ( context . Background ( ) )
}
return executor
}
2022-11-16 09:10:58 +00:00
func ( e * GeneralOrderExecutor ) DisableNotify ( ) {
e . disableNotify = true
}
2023-03-23 04:51:52 +00:00
func ( e * GeneralOrderExecutor ) SetMaxRetries ( maxRetries uint ) {
e . maxRetries = maxRetries
}
2022-09-16 04:19:30 +00:00
func ( e * GeneralOrderExecutor ) startMarginAssetUpdater ( ctx context . Context ) {
2024-08-18 05:16:55 +00:00
marginService , ok := e . exchange . ( types . MarginBorrowRepayService )
2022-09-16 04:19:30 +00:00
if ! ok {
log . Warnf ( "session %s (%T) exchange does not support MarginBorrowRepayService" , e . session . Name , e . session . Exchange )
return
}
go e . marginAssetMaxBorrowableUpdater ( ctx , 30 * time . Minute , marginService , e . position . Market )
}
2023-09-26 12:42:38 +00:00
func ( e * GeneralOrderExecutor ) updateMarginAssetMaxBorrowable (
ctx context . Context , marginService types . MarginBorrowRepayService , market types . Market ,
) {
2022-09-19 01:10:59 +00:00
maxBorrowable , err := marginService . QueryMarginAssetMaxBorrowable ( ctx , market . BaseCurrency )
if err != nil {
2024-01-07 11:08:54 +00:00
log . WithError ( err ) . Warnf ( "can not query margin base asset %s max borrowable" , market . BaseCurrency )
2022-09-19 01:10:59 +00:00
} else {
log . Infof ( "updating margin base asset %s max borrowable amount: %f" , market . BaseCurrency , maxBorrowable . Float64 ( ) )
e . marginBaseMaxBorrowable = maxBorrowable
}
maxBorrowable , err = marginService . QueryMarginAssetMaxBorrowable ( ctx , market . QuoteCurrency )
if err != nil {
2024-01-07 11:08:54 +00:00
log . WithError ( err ) . Warnf ( "can not query margin quote asset %s max borrowable" , market . QuoteCurrency )
2022-09-19 01:10:59 +00:00
} else {
log . Infof ( "updating margin quote asset %s max borrowable amount: %f" , market . QuoteCurrency , maxBorrowable . Float64 ( ) )
e . marginQuoteMaxBorrowable = maxBorrowable
}
}
2023-09-26 12:42:38 +00:00
func ( e * GeneralOrderExecutor ) marginAssetMaxBorrowableUpdater (
ctx context . Context , interval time . Duration , marginService types . MarginBorrowRepayService , market types . Market ,
) {
2022-09-19 01:10:59 +00:00
t := time . NewTicker ( util . MillisecondsJitter ( interval , 500 ) )
defer t . Stop ( )
2022-09-16 04:19:30 +00:00
2022-09-19 01:25:54 +00:00
e . updateMarginAssetMaxBorrowable ( ctx , marginService , market )
2022-09-16 04:19:30 +00:00
for {
select {
case <- ctx . Done ( ) :
return
2022-09-19 01:10:59 +00:00
case <- t . C :
e . updateMarginAssetMaxBorrowable ( ctx , marginService , market )
2022-09-16 04:19:30 +00:00
}
}
2022-06-18 08:31:53 +00:00
}
func ( e * GeneralOrderExecutor ) BindEnvironment ( environ * Environment ) {
e . tradeCollector . OnProfit ( func ( trade types . Trade , profit * types . Profit ) {
environ . RecordPosition ( e . position , trade , profit )
} )
}
func ( e * GeneralOrderExecutor ) BindTradeStats ( tradeStats * types . TradeStats ) {
e . tradeCollector . OnProfit ( func ( trade types . Trade , profit * types . Profit ) {
if profit == nil {
return
}
2022-07-05 03:14:50 +00:00
tradeStats . Add ( profit )
2022-06-18 08:31:53 +00:00
} )
}
2022-06-19 04:29:36 +00:00
func ( e * GeneralOrderExecutor ) BindProfitStats ( profitStats * types . ProfitStats ) {
2022-06-18 08:31:53 +00:00
e . tradeCollector . OnProfit ( func ( trade types . Trade , profit * types . Profit ) {
profitStats . AddTrade ( trade )
if profit == nil {
return
}
profitStats . AddProfit ( * profit )
2022-07-08 08:43:32 +00:00
2023-03-25 17:32:47 +00:00
if ! e . disableNotify {
Notify ( profit )
Notify ( profitStats )
}
2022-06-18 08:31:53 +00:00
} )
}
2022-11-16 09:10:58 +00:00
func ( e * GeneralOrderExecutor ) Bind ( ) {
2022-06-18 08:31:53 +00:00
e . activeMakerOrders . BindStream ( e . session . UserDataStream )
e . orderStore . BindStream ( e . session . UserDataStream )
2022-11-16 09:10:58 +00:00
if ! e . disableNotify {
2022-11-09 08:43:51 +00:00
// trade notify
e . tradeCollector . OnTrade ( func ( trade types . Trade , profit , netProfit fixedpoint . Value ) {
Notify ( trade )
} )
2022-06-18 08:31:53 +00:00
2022-11-09 08:43:51 +00:00
e . tradeCollector . OnPositionUpdate ( func ( position * types . Position ) {
Notify ( position )
} )
}
2022-06-18 08:31:53 +00:00
e . tradeCollector . BindStream ( e . session . UserDataStream )
}
2022-06-30 16:57:19 +00:00
// CancelOrders cancels the given order objects directly
2022-06-26 08:13:58 +00:00
func ( e * GeneralOrderExecutor ) CancelOrders ( ctx context . Context , orders ... types . Order ) error {
2022-08-11 05:49:16 +00:00
err := e . session . Exchange . CancelOrders ( ctx , orders ... )
if err != nil { // Retry once
err = e . session . Exchange . CancelOrders ( ctx , orders ... )
}
return err
2022-06-26 08:13:58 +00:00
}
2023-03-02 08:16:14 +00:00
func ( e * GeneralOrderExecutor ) SetLogger ( logger log . FieldLogger ) {
e . logger = logger
}
2023-09-26 12:42:38 +00:00
func ( e * GeneralOrderExecutor ) SubmitOrders (
ctx context . Context , submitOrders ... types . SubmitOrder ,
) ( types . OrderSlice , error ) {
2022-06-18 08:31:53 +00:00
formattedOrders , err := e . session . FormatOrders ( submitOrders )
if err != nil {
2022-06-19 07:57:59 +00:00
return nil , err
2022-06-18 08:31:53 +00:00
}
2023-02-23 15:34:26 +00:00
orderCreateCallback := func ( createdOrder types . Order ) {
e . orderStore . Add ( createdOrder )
e . activeMakerOrders . Add ( createdOrder )
2022-06-18 08:31:53 +00:00
}
2022-09-09 09:55:51 +00:00
2023-07-12 09:16:46 +00:00
defer e . tradeCollector . Process ( )
2023-03-23 04:51:52 +00:00
if e . maxRetries == 0 {
createdOrders , _ , err := BatchPlaceOrder ( ctx , e . session . Exchange , orderCreateCallback , formattedOrders ... )
return createdOrders , err
}
2023-03-02 08:57:29 +00:00
createdOrders , _ , err := BatchRetryPlaceOrder ( ctx , e . session . Exchange , nil , orderCreateCallback , e . logger , formattedOrders ... )
2023-03-01 07:29:26 +00:00
return createdOrders , err
2022-06-18 08:31:53 +00:00
}
2022-09-09 05:57:39 +00:00
type OpenPositionOptions struct {
// Long is for open a long position
2022-09-12 15:24:37 +00:00
// Long or Short must be set, avoid loading it from the config file
// it should be set from the strategy code
Long bool ` json:"-" yaml:"-" `
2022-09-09 05:57:39 +00:00
// Short is for open a short position
// Long or Short must be set
2022-09-12 15:24:37 +00:00
Short bool ` json:"-" yaml:"-" `
2022-09-09 05:57:39 +00:00
// Leverage is used for leveraged position and account
2022-09-12 15:48:40 +00:00
// Leverage is not effected when using non-leverage spot account
2022-09-22 04:01:26 +00:00
Leverage fixedpoint . Value ` json:"leverage,omitempty" modifiable:"true" `
2022-09-09 05:57:39 +00:00
2022-09-12 15:48:40 +00:00
// Quantity will be used first, it will override the leverage if it's given
2022-09-29 11:15:10 +00:00
Quantity fixedpoint . Value ` json:"quantity,omitempty" modifiable:"true" `
2022-09-09 09:50:21 +00:00
// LimitOrder set to true to open a position with a limit order
2022-09-22 04:01:26 +00:00
// default is false, and will send MarketOrder
LimitOrder bool ` json:"limitOrder,omitempty" modifiable:"true" `
2022-09-09 09:50:21 +00:00
2022-09-12 15:24:37 +00:00
// LimitOrderTakerRatio is used when LimitOrder = true, it adjusts the price of the limit order with a ratio.
2022-09-09 09:50:21 +00:00
// So you can ensure that the limit order can be a taker order. Higher the ratio, higher the chance it could be a taker order.
2022-09-12 15:48:40 +00:00
//
// limitOrderTakerRatio is the price ratio to adjust your limit order as a taker order. e.g., 0.1%
// for sell order, 0.1% ratio means your final price = price * (1 - 0.1%)
// for buy order, 0.1% ratio means your final price = price * (1 + 0.1%)
// this is only enabled when the limitOrder option set to true
2022-09-12 15:24:37 +00:00
LimitOrderTakerRatio fixedpoint . Value ` json:"limitOrderTakerRatio,omitempty" `
Price fixedpoint . Value ` json:"-" yaml:"-" `
Tags [ ] string ` json:"-" yaml:"-" `
2022-09-09 05:57:39 +00:00
}
2023-09-26 12:42:38 +00:00
func ( e * GeneralOrderExecutor ) reduceQuantityAndSubmitOrder (
ctx context . Context , price fixedpoint . Value , submitOrder types . SubmitOrder ,
) ( types . OrderSlice , error ) {
2022-09-23 17:25:28 +00:00
var err error
for i := 0 ; i < submitOrderRetryLimit ; i ++ {
q := submitOrder . Quantity . Mul ( fixedpoint . One . Sub ( quantityReduceDelta ) )
2022-11-26 16:25:29 +00:00
if ! e . session . Futures && ! e . session . Margin {
2022-10-18 10:59:04 +00:00
if submitOrder . Side == types . SideTypeSell {
if baseBalance , ok := e . session . GetAccount ( ) . Balance ( e . position . Market . BaseCurrency ) ; ok {
q = fixedpoint . Min ( q , baseBalance . Available )
}
} else {
if quoteBalance , ok := e . session . GetAccount ( ) . Balance ( e . position . Market . QuoteCurrency ) ; ok {
q = fixedpoint . Min ( q , quoteBalance . Available . Div ( price ) )
}
2022-09-28 11:06:37 +00:00
}
}
log . Warnf ( "retrying order, adjusting order quantity: %v -> %v" , submitOrder . Quantity , q )
2022-09-23 17:25:28 +00:00
submitOrder . Quantity = q
if e . position . Market . IsDustQuantity ( submitOrder . Quantity , price ) {
2023-06-06 07:50:07 +00:00
return nil , types . NewZeroAssetError ( fmt . Errorf ( "dust quantity, quantity = %f, price = %f" , submitOrder . Quantity . Float64 ( ) , price . Float64 ( ) ) )
2022-09-23 17:25:28 +00:00
}
2022-09-22 11:26:18 +00:00
createdOrder , err2 := e . SubmitOrders ( ctx , submitOrder )
if err2 != nil {
2022-09-23 17:25:28 +00:00
// collect the error object
err = multierr . Append ( err , err2 )
2022-09-22 11:26:18 +00:00
continue
}
2022-09-23 17:25:28 +00:00
2022-09-22 11:26:18 +00:00
log . Infof ( "created order: %+v" , createdOrder )
return createdOrder , nil
}
2022-09-23 17:25:28 +00:00
return nil , multierr . Append ( ErrExceededSubmitOrderRetryLimit , err )
2022-09-22 11:26:18 +00:00
}
2022-09-22 04:01:26 +00:00
2022-11-21 03:13:19 +00:00
// Create new submitOrder from OpenPositionOptions.
// @param ctx: golang context type.
// @param options: OpenPositionOptions to control the generated SubmitOrder in a higher level way. Notice that the Price in options will be updated as the submitOrder price.
// @return *types.SubmitOrder: SubmitOrder with calculated quantity and price.
// @return error: Error message.
2023-09-26 12:42:38 +00:00
func ( e * GeneralOrderExecutor ) NewOrderFromOpenPosition (
ctx context . Context , options * OpenPositionOptions ,
) ( * types . SubmitOrder , error ) {
2022-09-12 15:24:37 +00:00
price := options . Price
2022-09-09 05:57:39 +00:00
submitOrder := types . SubmitOrder {
Symbol : e . position . Symbol ,
Type : types . OrderTypeMarket ,
MarginSideEffect : types . SideEffectTypeMarginBuy ,
2022-09-10 18:36:45 +00:00
Tag : strings . Join ( options . Tags , "," ) ,
2022-09-09 05:57:39 +00:00
}
2022-09-28 11:06:37 +00:00
baseBalance , _ := e . session . GetAccount ( ) . Balance ( e . position . Market . BaseCurrency )
2022-09-19 05:12:49 +00:00
// FIXME: fix the max quote borrowing checking
// quoteBalance, _ := e.session.Account.Balance(e.position.Market.QuoteCurrency)
2022-09-12 15:24:37 +00:00
if ! options . LimitOrderTakerRatio . IsZero ( ) {
2022-09-23 17:27:28 +00:00
if options . Price . IsZero ( ) {
return nil , fmt . Errorf ( "OpenPositionOptions.Price is zero, can not adjust limit taker order price, options given: %+v" , options )
}
2022-09-09 05:57:39 +00:00
if options . Long {
// use higher price to buy (this ensures that our order will be filled)
2022-09-12 15:24:37 +00:00
price = price . Mul ( one . Add ( options . LimitOrderTakerRatio ) )
2022-11-09 08:43:51 +00:00
options . Price = price
2022-09-09 05:57:39 +00:00
} else if options . Short {
// use lower price to sell (this ensures that our order will be filled)
2022-09-12 15:24:37 +00:00
price = price . Mul ( one . Sub ( options . LimitOrderTakerRatio ) )
2022-11-09 08:43:51 +00:00
options . Price = price
2022-09-09 05:57:39 +00:00
}
}
2022-09-22 04:01:26 +00:00
if options . LimitOrder {
2022-09-09 05:57:39 +00:00
submitOrder . Type = types . OrderTypeLimit
submitOrder . Price = price
}
quantity := options . Quantity
if options . Long {
if quantity . IsZero ( ) {
2022-09-09 09:40:17 +00:00
quoteQuantity , err := CalculateQuoteQuantity ( ctx , e . session , e . position . QuoteCurrency , options . Leverage )
2022-09-09 05:57:39 +00:00
if err != nil {
2022-09-22 04:01:26 +00:00
return nil , err
2022-09-09 05:57:39 +00:00
}
2022-09-22 11:26:18 +00:00
2023-06-06 07:50:07 +00:00
if price . IsZero ( ) {
return nil , errors . New ( "unable to calculate quantity: zero price given" )
}
2022-09-09 05:57:39 +00:00
quantity = quoteQuantity . Div ( price )
}
2023-06-06 07:50:07 +00:00
2022-09-22 11:26:18 +00:00
if e . position . Market . IsDustQuantity ( quantity , price ) {
2023-06-06 07:50:07 +00:00
log . Errorf ( "can not submit order: dust quantity, quantity = %f, price = %f" , quantity . Float64 ( ) , price . Float64 ( ) )
2022-09-22 04:01:26 +00:00
return nil , nil
}
2022-09-09 05:57:39 +00:00
2022-09-19 01:10:59 +00:00
quoteQuantity := quantity . Mul ( price )
2022-09-19 08:00:12 +00:00
if e . session . Margin && ! e . marginQuoteMaxBorrowable . IsZero ( ) && quoteQuantity . Compare ( e . marginQuoteMaxBorrowable ) > 0 {
2022-09-19 01:10:59 +00:00
log . Warnf ( "adjusting quantity %f according to the max margin quote borrowable amount: %f" , quantity . Float64 ( ) , e . marginQuoteMaxBorrowable . Float64 ( ) )
quantity = AdjustQuantityByMaxAmount ( quantity , price , e . marginQuoteMaxBorrowable )
}
2022-09-09 05:57:39 +00:00
submitOrder . Side = types . SideTypeBuy
submitOrder . Quantity = quantity
2022-11-09 08:43:51 +00:00
return & submitOrder , nil
2022-09-09 05:57:39 +00:00
} else if options . Short {
if quantity . IsZero ( ) {
var err error
2022-09-09 09:40:17 +00:00
quantity , err = CalculateBaseQuantity ( e . session , e . position . Market , price , quantity , options . Leverage )
2022-09-09 05:57:39 +00:00
if err != nil {
2022-09-22 04:01:26 +00:00
return nil , err
2022-09-09 05:57:39 +00:00
}
}
2022-09-22 11:26:18 +00:00
if e . position . Market . IsDustQuantity ( quantity , price ) {
2022-09-22 04:01:26 +00:00
log . Warnf ( "dust quantity: %v" , quantity )
return nil , nil
}
2022-09-09 05:57:39 +00:00
2022-09-19 08:00:12 +00:00
if e . session . Margin && ! e . marginBaseMaxBorrowable . IsZero ( ) && quantity . Sub ( baseBalance . Available ) . Compare ( e . marginBaseMaxBorrowable ) > 0 {
2022-09-19 01:10:59 +00:00
log . Warnf ( "adjusting %f quantity according to the max margin base borrowable amount: %f" , quantity . Float64 ( ) , e . marginBaseMaxBorrowable . Float64 ( ) )
2022-09-19 05:12:49 +00:00
// quantity = fixedpoint.Min(quantity, e.marginBaseMaxBorrowable)
quantity = baseBalance . Available . Add ( e . marginBaseMaxBorrowable )
2022-09-19 01:10:59 +00:00
}
2022-09-09 05:57:39 +00:00
submitOrder . Side = types . SideTypeSell
submitOrder . Quantity = quantity
2022-11-09 08:43:51 +00:00
return & submitOrder , nil
2022-09-09 05:57:39 +00:00
}
2022-09-22 04:01:26 +00:00
return nil , errors . New ( "options Long or Short must be set" )
2022-09-09 05:57:39 +00:00
}
2022-11-21 03:13:19 +00:00
// OpenPosition sends the orders generated from OpenPositionOptions to the exchange by calling SubmitOrders or reduceQuantityAndSubmitOrder.
// @param ctx: golang context type.
// @param options: OpenPositionOptions to control the generated SubmitOrder in a higher level way. Notice that the Price in options will be updated as the submitOrder price.
// @return types.OrderSlice: Created orders with information from exchange.
// @return error: Error message.
2023-09-26 12:42:38 +00:00
func ( e * GeneralOrderExecutor ) OpenPosition (
ctx context . Context , options OpenPositionOptions ,
) ( types . OrderSlice , error ) {
2023-06-29 05:29:31 +00:00
if e . position . IsClosing ( ) {
return nil , errors . Wrap ( ErrPositionAlreadyClosing , "unable to open position" )
}
2022-11-21 03:13:19 +00:00
submitOrder , err := e . NewOrderFromOpenPosition ( ctx , & options )
2022-11-09 08:43:51 +00:00
if err != nil {
return nil , err
}
2023-06-06 07:50:07 +00:00
2022-11-09 08:43:51 +00:00
if submitOrder == nil {
return nil , nil
}
2023-06-06 07:50:07 +00:00
2022-11-09 08:43:51 +00:00
price := options . Price
side := "long"
if submitOrder . Side == types . SideTypeSell {
side = "short"
}
2023-06-06 07:50:07 +00:00
Notify ( "Opening %s %s position with quantity %f at price %f" , e . position . Symbol , side , submitOrder . Quantity . Float64 ( ) , price . Float64 ( ) )
2022-11-09 08:43:51 +00:00
createdOrder , err := e . SubmitOrders ( ctx , * submitOrder )
if err == nil {
return createdOrder , nil
}
2023-07-09 13:23:42 +00:00
log . WithError ( err ) . Errorf ( "unable to submit order: %v" , err )
log . Infof ( "reduce quantity and retry order" )
2022-11-09 08:43:51 +00:00
return e . reduceQuantityAndSubmitOrder ( ctx , price , * submitOrder )
}
2022-06-30 16:57:19 +00:00
// GracefulCancelActiveOrderBook cancels the orders from the active orderbook.
2022-11-02 04:34:04 +00:00
func ( e * GeneralOrderExecutor ) GracefulCancelActiveOrderBook ( ctx context . Context , activeOrders * ActiveOrderBook ) error {
2022-07-14 03:46:19 +00:00
if activeOrders . NumOfOrders ( ) == 0 {
return nil
}
2022-11-02 04:25:34 +00:00
2023-07-20 04:44:57 +00:00
defer e . tradeCollector . Process ( )
2022-06-18 08:31:53 +00:00
2023-07-20 04:44:57 +00:00
op := func ( ) error { return activeOrders . GracefulCancel ( ctx , e . session . Exchange ) }
return backoff . RetryGeneral ( ctx , op )
2022-06-18 08:31:53 +00:00
}
2022-10-17 04:38:58 +00:00
// GracefulCancel cancels all active maker orders if orders are not given, otherwise cancel all the given orders
func ( e * GeneralOrderExecutor ) GracefulCancel ( ctx context . Context , orders ... types . Order ) error {
2022-11-02 04:34:04 +00:00
if err := e . activeMakerOrders . GracefulCancel ( ctx , e . session . Exchange , orders ... ) ; err != nil {
return errors . Wrap ( err , "graceful cancel error" )
}
return nil
2022-10-17 04:38:58 +00:00
}
2023-06-29 05:29:31 +00:00
var ErrPositionAlreadyClosing = errors . New ( "position is already in closing process" )
2023-06-28 10:09:10 +00:00
2022-09-10 18:01:48 +00:00
// ClosePosition closes the current position by a percentage.
// percentage 0.1 means close 10% position
// tag is the order tag you want to attach, you may pass multiple tags, the tags will be combined into one tag string by commas.
2022-06-27 10:17:57 +00:00
func ( e * GeneralOrderExecutor ) ClosePosition ( ctx context . Context , percentage fixedpoint . Value , tags ... string ) error {
2023-06-28 10:09:10 +00:00
if ! e . position . SetClosing ( true ) {
return ErrPositionAlreadyClosing
2022-06-18 08:31:53 +00:00
}
2023-06-28 10:09:10 +00:00
defer e . position . SetClosing ( false )
2022-06-18 08:31:53 +00:00
2023-06-28 10:09:10 +00:00
submitOrder := e . position . NewMarketCloseOrder ( percentage )
if submitOrder == nil {
2022-09-19 05:23:23 +00:00
return nil
}
2022-10-07 05:06:32 +00:00
if e . session . Futures { // Futures: Use base qty in e.position
2023-03-29 10:28:25 +00:00
submitOrder . Quantity = e . position . GetBase ( ) . Abs ( )
submitOrder . ReduceOnly = true
2023-03-29 09:46:54 +00:00
2022-10-07 05:06:32 +00:00
if e . position . IsLong ( ) {
submitOrder . Side = types . SideTypeSell
2022-10-07 05:28:24 +00:00
} else if e . position . IsShort ( ) {
2022-10-07 05:06:32 +00:00
submitOrder . Side = types . SideTypeBuy
2022-10-07 05:28:24 +00:00
} else {
2023-03-29 09:46:54 +00:00
return fmt . Errorf ( "unexpected position side: %+v" , e . position )
2022-10-07 05:28:24 +00:00
}
2022-10-07 05:06:32 +00:00
} else { // Spot and spot margin
// check base balance and adjust the close position order
if e . position . IsLong ( ) {
if baseBalance , ok := e . session . Account . Balance ( e . position . Market . BaseCurrency ) ; ok {
submitOrder . Quantity = fixedpoint . Min ( submitOrder . Quantity , baseBalance . Available )
2022-09-19 05:23:23 +00:00
}
2022-10-07 05:06:32 +00:00
if submitOrder . Quantity . IsZero ( ) {
return fmt . Errorf ( "insufficient base balance, can not sell: %+v" , submitOrder )
}
} else if e . position . IsShort ( ) {
2024-03-20 17:48:08 +00:00
if quoteBalance , ok := e . session . Account . Balance ( e . position . Market . QuoteCurrency ) ; ok {
ticker , err := e . session . Exchange . QueryTicker ( ctx , e . position . Symbol )
if err != nil {
return err
2022-10-07 05:06:32 +00:00
}
2024-03-20 17:48:08 +00:00
currentPrice := ticker . Sell
submitOrder . Quantity = AdjustQuantityByMaxAmount ( submitOrder . Quantity , currentPrice , quoteBalance . Available )
if submitOrder . Quantity . IsZero ( ) {
return fmt . Errorf ( "insufficient quote balance, can not buy: %+v" , submitOrder )
}
}
2022-10-07 05:06:32 +00:00
}
2022-09-11 09:46:23 +00:00
}
2022-09-10 18:33:32 +00:00
tagStr := strings . Join ( tags , "," )
submitOrder . Tag = tagStr
2023-03-29 09:46:54 +00:00
Notify ( "Closing %s position %s with tags: %s" , e . symbol , percentage . Percentage ( ) , tagStr )
2022-09-10 18:36:45 +00:00
2023-06-28 10:09:10 +00:00
createdOrders , err := e . SubmitOrders ( ctx , * submitOrder )
if err != nil {
return err
}
2023-06-28 10:11:00 +00:00
if queryOrderService , ok := e . session . Exchange . ( types . ExchangeOrderQueryService ) ; ok && ! IsBackTesting {
2023-06-28 10:09:10 +00:00
switch submitOrder . Type {
case types . OrderTypeMarket :
2023-06-29 09:17:32 +00:00
_ , err2 := retry . QueryOrderUntilFilled ( ctx , queryOrderService , createdOrders [ 0 ] . Symbol , createdOrders [ 0 ] . OrderID )
2023-06-29 02:56:07 +00:00
if err2 != nil {
log . WithError ( err2 ) . Errorf ( "unable to query order" )
}
2023-06-28 10:09:10 +00:00
}
}
return nil
2022-06-18 08:31:53 +00:00
}
2022-06-19 05:40:10 +00:00
2023-07-05 07:26:36 +00:00
func ( e * GeneralOrderExecutor ) TradeCollector ( ) * core . TradeCollector {
2022-06-19 05:40:10 +00:00
return e . tradeCollector
}
2022-06-26 08:13:58 +00:00
func ( e * GeneralOrderExecutor ) Session ( ) * ExchangeSession {
return e . session
}
func ( e * GeneralOrderExecutor ) Position ( ) * types . Position {
return e . position
}
2022-09-14 07:54:43 +00:00
// This implements PositionReader interface
func ( e * GeneralOrderExecutor ) CurrentPosition ( ) * types . Position {
return e . position
}
// This implements PositionResetter interface
func ( e * GeneralOrderExecutor ) ResetPosition ( ) error {
e . position . Reset ( )
return nil
}