2022-05-10 09:11:24 +00:00
package pivotshort
2022-05-09 21:11:22 +00:00
import (
"context"
"fmt"
2022-06-09 18:39:14 +00:00
"os"
2022-06-10 16:26:44 +00:00
"sort"
2022-06-09 16:49:32 +00:00
"sync"
2022-06-09 03:30:24 +00:00
2022-06-04 17:09:31 +00:00
"github.com/sirupsen/logrus"
2022-05-09 21:11:22 +00:00
"github.com/c9s/bbgo/pkg/bbgo"
"github.com/c9s/bbgo/pkg/fixedpoint"
2022-05-13 10:05:25 +00:00
"github.com/c9s/bbgo/pkg/indicator"
2022-05-09 21:11:22 +00:00
"github.com/c9s/bbgo/pkg/types"
)
2022-05-10 09:11:24 +00:00
const ID = "pivotshort"
2022-05-09 21:11:22 +00:00
var log = logrus . WithField ( "strategy" , ID )
func init ( ) {
bbgo . RegisterStrategy ( ID , & Strategy { } )
}
type IntervalWindowSetting struct {
types . IntervalWindow
}
2022-06-09 09:36:22 +00:00
// BreakLow -- when price breaks the previous pivot low, we set a trade entry
type BreakLow struct {
2022-06-09 10:16:32 +00:00
Ratio fixedpoint . Value ` json:"ratio" `
2022-06-10 03:36:04 +00:00
MarketOrder bool ` json:"marketOrder" `
BounceRatio fixedpoint . Value ` json:"bounceRatio" `
2022-06-09 10:16:32 +00:00
Quantity fixedpoint . Value ` json:"quantity" `
StopEMARange fixedpoint . Value ` json:"stopEMARange" `
StopEMA * types . IntervalWindow ` json:"stopEMA" `
2022-06-09 09:36:22 +00:00
}
2022-06-10 16:26:44 +00:00
type BounceShort struct {
2022-06-11 08:41:56 +00:00
Enabled bool ` json:"enabled" `
2022-06-10 16:26:44 +00:00
types . IntervalWindow
MinDistance fixedpoint . Value ` json:"minDistance" `
NumLayers int ` json:"numLayers" `
LayerSpread fixedpoint . Value ` json:"layerSpread" `
Quantity fixedpoint . Value ` json:"quantity" `
Ratio fixedpoint . Value ` json:"ratio" `
}
2022-06-03 08:38:06 +00:00
type Entry struct {
2022-06-09 03:30:24 +00:00
CatBounceRatio fixedpoint . Value ` json:"catBounceRatio" `
NumLayers int ` json:"numLayers" `
TotalQuantity fixedpoint . Value ` json:"totalQuantity" `
2022-06-03 08:38:06 +00:00
Quantity fixedpoint . Value ` json:"quantity" `
MarginSideEffect types . MarginOrderSideEffectType ` json:"marginOrderSideEffect" `
}
2022-06-09 18:39:14 +00:00
type CumulatedVolume struct {
Enabled bool ` json:"enabled" `
MinQuoteVolume fixedpoint . Value ` json:"minQuoteVolume" `
Window int ` json:"window" `
}
2022-06-03 08:38:06 +00:00
type Exit struct {
2022-06-09 18:39:14 +00:00
RoiStopLossPercentage fixedpoint . Value ` json:"roiStopLossPercentage" `
RoiTakeProfitPercentage fixedpoint . Value ` json:"roiTakeProfitPercentage" `
RoiMinTakeProfitPercentage fixedpoint . Value ` json:"roiMinTakeProfitPercentage" `
2022-06-09 09:36:22 +00:00
LowerShadowRatio fixedpoint . Value ` json:"lowerShadowRatio" `
2022-06-09 18:39:14 +00:00
CumulatedVolume * CumulatedVolume ` json:"cumulatedVolume" `
2022-06-09 09:36:22 +00:00
MarginSideEffect types . MarginOrderSideEffectType ` json:"marginOrderSideEffect" `
2022-06-03 08:38:06 +00:00
}
2022-05-09 21:11:22 +00:00
type Strategy struct {
2022-05-12 11:27:57 +00:00
* bbgo . Graceful
* bbgo . Notifiability
* bbgo . Persistence
2022-06-05 23:29:25 +00:00
Environment * bbgo . Environment
Symbol string ` json:"symbol" `
Market types . Market
2022-06-10 07:34:57 +00:00
// pivot interval and window
types . IntervalWindow
2022-05-12 11:27:57 +00:00
// persistence fields
2022-06-18 08:31:53 +00:00
Position * types . Position ` persistence:"position" `
ProfitStats * types . ProfitStats ` persistence:"profit_stats" `
TradeStats * types . TradeStats ` persistence:"trade_stats" `
2022-05-09 21:11:22 +00:00
2022-06-09 09:36:22 +00:00
BreakLow BreakLow ` json:"breakLow" `
2022-06-10 16:26:44 +00:00
BounceShort * BounceShort ` json:"bounceShort" `
Entry Entry ` json:"entry" `
Exit Exit ` json:"exit" `
2022-05-09 21:11:22 +00:00
2022-06-18 03:45:24 +00:00
session * bbgo . ExchangeSession
2022-06-18 08:31:53 +00:00
orderExecutor * bbgo . GeneralOrderExecutor
2022-05-09 21:11:22 +00:00
2022-06-10 16:26:44 +00:00
lastLow fixedpoint . Value
pivot * indicator . Pivot
resistancePivot * indicator . Pivot
stopEWMA * indicator . EWMA
pivotLowPrices [ ] fixedpoint . Value
resistancePrices [ ] float64
currentBounceShortPrice fixedpoint . Value
2022-05-12 11:27:57 +00:00
// StrategyController
bbgo . StrategyController
2022-05-09 21:11:22 +00:00
}
func ( s * Strategy ) ID ( ) string {
return ID
}
func ( s * Strategy ) Subscribe ( session * bbgo . ExchangeSession ) {
log . Infof ( "subscribe %s" , s . Symbol )
2022-05-19 01:48:36 +00:00
session . Subscribe ( types . KLineChannel , s . Symbol , types . SubscribeOptions { Interval : s . Interval } )
2022-06-05 23:29:25 +00:00
session . Subscribe ( types . KLineChannel , s . Symbol , types . SubscribeOptions { Interval : types . Interval1m } )
2022-06-10 16:26:44 +00:00
2022-06-11 08:41:56 +00:00
if s . BounceShort != nil && s . BounceShort . Enabled {
2022-06-10 16:26:44 +00:00
session . Subscribe ( types . KLineChannel , s . Symbol , types . SubscribeOptions { Interval : s . BounceShort . Interval } )
}
2022-06-05 23:29:25 +00:00
}
2022-06-10 03:36:04 +00:00
func ( s * Strategy ) useQuantityOrBaseBalance ( quantity fixedpoint . Value ) fixedpoint . Value {
2022-06-05 04:47:15 +00:00
if quantity . IsZero ( ) {
if balance , ok := s . session . Account . Balance ( s . Market . BaseCurrency ) ; ok {
2022-06-05 04:58:12 +00:00
s . Notify ( "sell quantity is not set, submitting sell with all base balance: %s" , balance . Available . String ( ) )
2022-06-05 04:47:15 +00:00
quantity = balance . Available
}
}
2022-06-05 04:58:12 +00:00
if quantity . IsZero ( ) {
log . Errorf ( "quantity is zero, can not submit sell order, please check settings" )
}
2022-06-10 03:36:04 +00:00
return quantity
}
2022-06-18 04:30:42 +00:00
func ( s * Strategy ) placeLimitSell ( ctx context . Context , price , quantity fixedpoint . Value ) {
_ = s . orderExecutor . SubmitOrders ( ctx , types . SubmitOrder {
2022-06-05 04:47:15 +00:00
Symbol : s . Symbol ,
2022-06-10 03:36:04 +00:00
Price : price ,
2022-06-05 04:47:15 +00:00
Side : types . SideTypeSell ,
2022-06-10 03:36:04 +00:00
Type : types . OrderTypeLimit ,
2022-06-05 04:47:15 +00:00
Quantity : quantity ,
2022-06-09 09:36:22 +00:00
MarginSideEffect : types . SideEffectTypeMarginBuy ,
2022-06-10 03:36:04 +00:00
} )
}
2022-06-05 04:47:15 +00:00
2022-06-18 04:30:42 +00:00
func ( s * Strategy ) placeMarketSell ( ctx context . Context , quantity fixedpoint . Value ) {
_ = s . orderExecutor . SubmitOrders ( ctx , types . SubmitOrder {
2022-06-10 03:36:04 +00:00
Symbol : s . Symbol ,
Side : types . SideTypeSell ,
Type : types . OrderTypeMarket ,
Quantity : quantity ,
MarginSideEffect : types . SideEffectTypeMarginBuy ,
} )
2022-06-05 04:47:15 +00:00
}
2022-05-09 21:11:22 +00:00
func ( s * Strategy ) ClosePosition ( ctx context . Context , percentage fixedpoint . Value ) error {
2022-06-09 09:36:22 +00:00
submitOrder := s . Position . NewMarketCloseOrder ( percentage ) // types.SubmitOrder{
2022-06-09 04:25:36 +00:00
if submitOrder == nil {
return nil
}
2022-05-09 21:11:22 +00:00
2022-06-02 13:42:05 +00:00
if s . session . Margin {
2022-06-03 08:38:06 +00:00
submitOrder . MarginSideEffect = s . Exit . MarginSideEffect
2022-06-02 13:42:05 +00:00
}
2022-05-09 21:11:22 +00:00
2022-06-09 09:36:22 +00:00
s . Notify ( "Closing %s position by %f" , s . Symbol , percentage . Float64 ( ) )
2022-06-18 04:30:42 +00:00
return s . orderExecutor . SubmitOrders ( ctx , * submitOrder )
2022-05-09 21:11:22 +00:00
}
2022-06-09 16:49:32 +00:00
2022-05-12 11:27:57 +00:00
func ( s * Strategy ) InstanceID ( ) string {
return fmt . Sprintf ( "%s:%s" , ID , s . Symbol )
}
2022-05-09 21:11:22 +00:00
func ( s * Strategy ) Run ( ctx context . Context , orderExecutor bbgo . OrderExecutor , session * bbgo . ExchangeSession ) error {
2022-06-18 03:45:24 +00:00
var instanceID = s . InstanceID ( )
2022-05-09 21:11:22 +00:00
if s . Position == nil {
s . Position = types . NewPositionFromMarket ( s . Market )
}
2022-06-04 17:48:56 +00:00
if s . ProfitStats == nil {
s . ProfitStats = types . NewProfitStats ( s . Market )
}
2022-06-18 04:30:42 +00:00
// trade stats
2022-06-09 16:49:32 +00:00
if s . TradeStats == nil {
2022-06-18 08:31:53 +00:00
s . TradeStats = & types . TradeStats { }
2022-06-09 16:49:32 +00:00
}
2022-06-18 07:27:11 +00:00
// initial required information
s . session = session
2022-06-18 08:31:53 +00:00
s . orderExecutor = bbgo . NewGeneralOrderExecutor ( session , s . Symbol , ID , instanceID , s . Position )
s . orderExecutor . BindEnvironment ( s . Environment )
2022-06-18 07:27:11 +00:00
s . orderExecutor . BindProfitStats ( s . ProfitStats , s . Notifiability . Notify )
s . orderExecutor . Bind ( s . Notifiability . Notify )
2022-05-09 21:11:22 +00:00
2022-06-09 04:34:12 +00:00
store , _ := session . MarketDataStore ( s . Symbol )
2022-06-10 16:26:44 +00:00
2022-06-10 07:34:57 +00:00
s . pivot = & indicator . Pivot { IntervalWindow : s . IntervalWindow }
2022-06-09 04:34:12 +00:00
s . pivot . Bind ( store )
2022-05-09 21:11:22 +00:00
2022-06-11 08:41:56 +00:00
if s . BounceShort != nil && s . BounceShort . Enabled {
2022-06-10 16:26:44 +00:00
s . resistancePivot = & indicator . Pivot { IntervalWindow : s . BounceShort . IntervalWindow }
s . resistancePivot . Bind ( store )
}
2022-06-09 10:16:32 +00:00
standardIndicator , _ := session . StandardIndicatorSet ( s . Symbol )
if s . BreakLow . StopEMA != nil {
2022-06-10 07:18:12 +00:00
s . stopEWMA = standardIndicator . EWMA ( * s . BreakLow . StopEMA )
2022-06-09 10:16:32 +00:00
}
2022-06-09 09:36:22 +00:00
s . lastLow = fixedpoint . Zero
2022-06-03 18:17:58 +00:00
2022-05-09 21:11:22 +00:00
session . UserDataStream . OnStart ( func ( ) {
2022-06-10 16:26:44 +00:00
lastKLine := s . preloadPivot ( s . pivot , store )
2022-06-10 07:34:57 +00:00
2022-06-10 16:26:44 +00:00
if s . resistancePivot != nil {
s . preloadPivot ( s . resistancePivot , store )
}
if lastKLine == nil {
return
}
2022-06-11 08:41:56 +00:00
if s . resistancePivot != nil {
lows := s . resistancePivot . Lows
minDistance := s . BounceShort . MinDistance . Float64 ( )
closePrice := lastKLine . Close . Float64 ( )
s . resistancePrices = findPossibleResistancePrices ( closePrice , minDistance , lows )
log . Infof ( "last price: %f, possible resistance prices: %+v" , closePrice , s . resistancePrices )
if len ( s . resistancePrices ) > 0 {
resistancePrice := fixedpoint . NewFromFloat ( s . resistancePrices [ 0 ] )
if resistancePrice . Compare ( s . currentBounceShortPrice ) != 0 {
log . Infof ( "updating resistance price... possible resistance prices: %+v" , s . resistancePrices )
2022-06-18 04:30:42 +00:00
_ = s . orderExecutor . GracefulCancel ( ctx )
2022-06-11 08:41:56 +00:00
s . currentBounceShortPrice = resistancePrice
s . placeBounceSellOrders ( ctx , s . currentBounceShortPrice , orderExecutor )
2022-06-11 08:33:21 +00:00
}
}
2022-06-10 07:18:12 +00:00
}
2022-05-09 21:11:22 +00:00
} )
2022-06-05 23:29:25 +00:00
// Always check whether you can open a short position or not
session . MarketDataStream . OnKLineClosed ( func ( kline types . KLine ) {
if kline . Symbol != s . Symbol || kline . Interval != types . Interval1m {
return
}
2022-06-09 03:46:14 +00:00
2022-06-09 09:36:22 +00:00
isPositionOpened := ! s . Position . IsClosed ( ) && ! s . Position . IsDust ( kline . Close )
if isPositionOpened && s . Position . IsShort ( ) {
2022-06-05 23:29:25 +00:00
// calculate return rate
2022-06-09 09:36:22 +00:00
// TODO: apply quantity to this formula
2022-06-09 16:49:32 +00:00
roi := s . Position . AverageCost . Sub ( kline . Close ) . Div ( s . Position . AverageCost )
if roi . Compare ( s . Exit . RoiStopLossPercentage . Neg ( ) ) < 0 {
2022-06-09 18:39:14 +00:00
// stop loss
s . Notify ( "%s ROI StopLoss triggered at price %f: Loss %s" , s . Symbol , kline . Close . Float64 ( ) , roi . Percentage ( ) )
s . closePosition ( ctx )
2022-06-09 04:25:36 +00:00
return
2022-06-09 18:39:14 +00:00
} else {
// take profit
if roi . Compare ( s . Exit . RoiTakeProfitPercentage ) > 0 { // force take profit
s . Notify ( "%s TakeProfit triggered at price %f: by ROI percentage %s" , s . Symbol , kline . Close . Float64 ( ) , roi . Percentage ( ) , kline )
s . closePosition ( ctx )
return
} else if ! s . Exit . RoiMinTakeProfitPercentage . IsZero ( ) && roi . Compare ( s . Exit . RoiMinTakeProfitPercentage ) > 0 {
if ! s . Exit . LowerShadowRatio . IsZero ( ) && kline . GetLowerShadowHeight ( ) . Div ( kline . Close ) . Compare ( s . Exit . LowerShadowRatio ) > 0 {
s . Notify ( "%s TakeProfit triggered at price %f: by shadow ratio %f" ,
s . Symbol ,
kline . Close . Float64 ( ) ,
kline . GetLowerShadowRatio ( ) . Float64 ( ) , kline )
s . closePosition ( ctx )
return
} else if s . Exit . CumulatedVolume != nil && s . Exit . CumulatedVolume . Enabled {
if klines , ok := store . KLinesOfInterval ( s . Interval ) ; ok {
var cbv = fixedpoint . Zero
var cqv = fixedpoint . Zero
for i := 0 ; i < s . Exit . CumulatedVolume . Window ; i ++ {
last := ( * klines ) [ len ( * klines ) - 1 - i ]
cqv = cqv . Add ( last . QuoteVolume )
cbv = cbv . Add ( last . Volume )
}
if cqv . Compare ( s . Exit . CumulatedVolume . MinQuoteVolume ) > 0 {
s . Notify ( "%s TakeProfit triggered at price %f: by cumulated volume (window: %d) %f > %f" ,
s . Symbol ,
kline . Close . Float64 ( ) ,
s . Exit . CumulatedVolume . Window ,
cqv . Float64 ( ) ,
s . Exit . CumulatedVolume . MinQuoteVolume . Float64 ( ) )
s . closePosition ( ctx )
return
}
}
}
2022-06-05 23:29:25 +00:00
}
}
2022-06-05 04:55:36 +00:00
}
2022-06-09 09:36:22 +00:00
if len ( s . pivotLowPrices ) == 0 {
return
}
previousLow := s . pivotLowPrices [ len ( s . pivotLowPrices ) - 1 ]
// truncate the pivot low prices
if len ( s . pivotLowPrices ) > 10 {
s . pivotLowPrices = s . pivotLowPrices [ len ( s . pivotLowPrices ) - 10 : ]
}
2022-06-10 07:18:12 +00:00
if s . stopEWMA != nil && ! s . BreakLow . StopEMARange . IsZero ( ) {
ema := fixedpoint . NewFromFloat ( s . stopEWMA . Last ( ) )
2022-06-09 10:16:32 +00:00
if ema . IsZero ( ) {
return
}
emaStopShortPrice := ema . Mul ( fixedpoint . One . Sub ( s . BreakLow . StopEMARange ) )
if kline . Close . Compare ( emaStopShortPrice ) < 0 {
return
}
}
2022-06-09 09:36:22 +00:00
ratio := fixedpoint . One . Sub ( s . BreakLow . Ratio )
breakPrice := previousLow . Mul ( ratio )
2022-06-11 08:41:56 +00:00
// if previous low is not break, skip
if kline . Close . Compare ( breakPrice ) >= 0 {
2022-06-09 09:36:22 +00:00
return
}
if ! s . Position . IsClosed ( ) && ! s . Position . IsDust ( kline . Close ) {
// s.Notify("skip opening %s position, which is not closed", s.Symbol, s.Position)
return
}
2022-06-18 04:30:42 +00:00
_ = s . orderExecutor . GracefulCancel ( ctx )
2022-06-09 09:36:22 +00:00
2022-06-10 03:36:04 +00:00
quantity := s . useQuantityOrBaseBalance ( s . BreakLow . Quantity )
if s . BreakLow . MarketOrder {
s . Notify ( "%s price %f breaks the previous low %f with ratio %f, submitting market sell to open a short position" , s . Symbol , kline . Close . Float64 ( ) , previousLow . Float64 ( ) , s . BreakLow . Ratio . Float64 ( ) )
2022-06-18 04:30:42 +00:00
s . placeMarketSell ( ctx , quantity )
2022-06-10 03:36:04 +00:00
} else {
sellPrice := kline . Close . Mul ( fixedpoint . One . Add ( s . BreakLow . BounceRatio ) )
2022-06-18 04:30:42 +00:00
s . placeLimitSell ( ctx , sellPrice , quantity )
2022-06-10 03:36:04 +00:00
}
2022-06-05 04:48:54 +00:00
} )
2022-06-10 16:26:44 +00:00
session . MarketDataStream . OnKLineClosed ( func ( kline types . KLine ) {
2022-06-11 08:41:56 +00:00
if s . BounceShort == nil || ! s . BounceShort . Enabled {
2022-06-10 16:26:44 +00:00
return
}
if kline . Symbol != s . Symbol || kline . Interval != s . BounceShort . Interval {
return
}
2022-06-11 08:41:56 +00:00
if s . resistancePivot != nil {
closePrice := kline . Close . Float64 ( )
minDistance := s . BounceShort . MinDistance . Float64 ( )
lows := s . resistancePivot . Lows
s . resistancePrices = findPossibleResistancePrices ( closePrice , minDistance , lows )
if len ( s . resistancePrices ) > 0 {
resistancePrice := fixedpoint . NewFromFloat ( s . resistancePrices [ 0 ] )
if resistancePrice . Compare ( s . currentBounceShortPrice ) != 0 {
log . Infof ( "updating resistance price... possible resistance prices: %+v" , s . resistancePrices )
2022-06-18 04:30:42 +00:00
_ = s . orderExecutor . GracefulCancel ( ctx )
2022-06-11 08:41:56 +00:00
s . currentBounceShortPrice = resistancePrice
s . placeBounceSellOrders ( ctx , s . currentBounceShortPrice , orderExecutor )
}
}
}
2022-06-10 16:26:44 +00:00
} )
2022-05-09 21:11:22 +00:00
session . MarketDataStream . OnKLineClosed ( func ( kline types . KLine ) {
if kline . Symbol != s . Symbol || kline . Interval != s . Interval {
return
}
2022-06-02 13:34:26 +00:00
2022-06-09 03:30:24 +00:00
if s . pivot . LastLow ( ) > 0.0 {
2022-06-09 09:36:22 +00:00
lastLow := fixedpoint . NewFromFloat ( s . pivot . LastLow ( ) )
2022-06-11 08:41:56 +00:00
if lastLow . Compare ( s . lastLow ) != 0 {
log . Infof ( "new pivot low detected: %f %s" , s . pivot . LastLow ( ) , kline . EndTime . Time ( ) )
}
2022-06-10 03:36:04 +00:00
s . lastLow = lastLow
s . pivotLowPrices = append ( s . pivotLowPrices , s . lastLow )
2022-05-09 21:11:22 +00:00
}
} )
2022-06-09 16:49:32 +00:00
s . Graceful . OnShutdown ( func ( ctx context . Context , wg * sync . WaitGroup ) {
2022-06-09 18:39:14 +00:00
_ , _ = fmt . Fprintln ( os . Stderr , s . TradeStats . String ( ) )
2022-06-09 16:49:32 +00:00
wg . Done ( )
} )
2022-05-09 21:11:22 +00:00
return nil
}
2022-06-09 03:30:24 +00:00
2022-06-09 18:39:14 +00:00
func ( s * Strategy ) closePosition ( ctx context . Context ) {
2022-06-18 04:30:42 +00:00
_ = s . orderExecutor . GracefulCancel ( ctx )
2022-06-18 07:27:11 +00:00
if err := s . orderExecutor . ClosePosition ( ctx , fixedpoint . One ) ; err != nil {
2022-06-09 18:39:14 +00:00
log . WithError ( err ) . Errorf ( "close position error" )
}
}
2022-06-09 03:30:24 +00:00
func ( s * Strategy ) findHigherPivotLow ( price fixedpoint . Value ) ( fixedpoint . Value , bool ) {
for l := len ( s . pivotLowPrices ) - 1 ; l > 0 ; l -- {
if s . pivotLowPrices [ l ] . Compare ( price ) > 0 {
return s . pivotLowPrices [ l ] , true
}
}
return price , false
}
2022-06-10 16:26:44 +00:00
func ( s * Strategy ) placeBounceSellOrders ( ctx context . Context , resistancePrice fixedpoint . Value , orderExecutor bbgo . OrderExecutor ) {
2022-06-09 03:30:24 +00:00
futuresMode := s . session . Futures || s . session . IsolatedFutures
2022-06-10 16:26:44 +00:00
totalQuantity := s . BounceShort . Quantity
numLayers := s . BounceShort . NumLayers
if numLayers == 0 {
numLayers = 1
2022-06-09 03:30:24 +00:00
}
2022-06-11 08:33:21 +00:00
2022-06-10 16:26:44 +00:00
numLayersF := fixedpoint . NewFromInt ( int64 ( numLayers ) )
2022-06-09 03:30:24 +00:00
2022-06-10 16:26:44 +00:00
layerSpread := s . BounceShort . LayerSpread
quantity := totalQuantity . Div ( numLayersF )
2022-06-11 08:33:21 +00:00
log . Infof ( "placing bounce short orders: resistance price = %f, layer quantity = %f, num of layers = %d" , resistancePrice . Float64 ( ) , quantity . Float64 ( ) , numLayers )
2022-06-10 16:26:44 +00:00
for i := 0 ; i < numLayers ; i ++ {
2022-06-09 03:30:24 +00:00
balances := s . session . GetAccount ( ) . Balances ( )
2022-06-17 11:19:51 +00:00
quoteBalance := balances [ s . Market . QuoteCurrency ]
baseBalance := balances [ s . Market . BaseCurrency ]
2022-06-09 03:30:24 +00:00
2022-06-10 16:26:44 +00:00
// price = (resistance_price * (1.0 - ratio)) * ((1.0 + layerSpread) * i)
2022-06-11 08:33:21 +00:00
price := resistancePrice . Mul ( fixedpoint . One . Sub ( s . BounceShort . Ratio ) )
spread := layerSpread . Mul ( fixedpoint . NewFromInt ( int64 ( i ) ) )
price = price . Add ( spread )
log . Infof ( "price = %f" , price . Float64 ( ) )
log . Infof ( "placing bounce short order #%d: price = %f, quantity = %f" , i , price . Float64 ( ) , quantity . Float64 ( ) )
2022-06-09 03:30:24 +00:00
if futuresMode {
2022-06-10 16:26:44 +00:00
if quantity . Mul ( price ) . Compare ( quoteBalance . Available ) <= 0 {
2022-06-18 07:27:11 +00:00
s . placeOrder ( ctx , price , quantity )
2022-06-09 03:30:24 +00:00
}
} else {
2022-06-10 16:26:44 +00:00
if quantity . Compare ( baseBalance . Available ) <= 0 {
2022-06-18 07:27:11 +00:00
s . placeOrder ( ctx , price , quantity )
2022-06-09 03:30:24 +00:00
}
}
}
}
2022-06-18 07:27:11 +00:00
func ( s * Strategy ) placeOrder ( ctx context . Context , price fixedpoint . Value , quantity fixedpoint . Value ) {
_ = s . orderExecutor . SubmitOrders ( ctx , types . SubmitOrder {
2022-06-09 03:30:24 +00:00
Symbol : s . Symbol ,
Side : types . SideTypeSell ,
Type : types . OrderTypeLimit ,
2022-06-10 16:26:44 +00:00
Price : price ,
Quantity : quantity ,
2022-06-18 07:27:11 +00:00
} )
2022-06-10 16:26:44 +00:00
}
2022-06-09 03:30:24 +00:00
2022-06-10 16:26:44 +00:00
func ( s * Strategy ) preloadPivot ( pivot * indicator . Pivot , store * bbgo . MarketDataStore ) * types . KLine {
klines , ok := store . KLinesOfInterval ( pivot . Interval )
if ! ok {
return nil
2022-06-09 03:30:24 +00:00
}
2022-06-10 16:26:44 +00:00
last := ( * klines ) [ len ( * klines ) - 1 ]
log . Infof ( "last %s price: %f" , s . Symbol , last . Close . Float64 ( ) )
log . Debugf ( "updating pivot indicator: %d klines" , len ( * klines ) )
for i := pivot . Window ; i < len ( * klines ) ; i ++ {
pivot . Update ( ( * klines ) [ 0 : i + 1 ] )
}
log . Infof ( "found %s %v previous lows: %v" , s . Symbol , pivot . IntervalWindow , pivot . Lows )
log . Infof ( "found %s %v previous highs: %v" , s . Symbol , pivot . IntervalWindow , pivot . Highs )
return & last
}
func findPossibleResistancePrices ( closePrice float64 , minDistance float64 , lows [ ] float64 ) [ ] float64 {
// sort float64 in increasing order
sort . Float64s ( lows )
var resistancePrices [ ] float64
for _ , low := range lows {
if low < closePrice {
continue
}
last := closePrice
if len ( resistancePrices ) > 0 {
last = resistancePrices [ len ( resistancePrices ) - 1 ]
}
if ( low / last ) < ( 1.0 + minDistance ) {
continue
}
resistancePrices = append ( resistancePrices , low )
}
return resistancePrices
2022-06-09 03:30:24 +00:00
}