From 0be414c77c2f12c9b4b624922aea5841e84c704c Mon Sep 17 00:00:00 2001 From: Reid McCamish <43561569+Reidmcc@users.noreply.github.com> Date: Fri, 21 Dec 2018 06:35:44 -0600 Subject: [PATCH] Add balanced fill tracking to avoid unecessary re-randomization (#78), closes #52 --- cmd/trade.go | 17 +++--- plugins/balancedLevelProvider.go | 91 ++++++++++++++++++++++---------- 2 files changed, 74 insertions(+), 34 deletions(-) diff --git a/cmd/trade.go b/cmd/trade.go index c987c583a..303c6c27d 100644 --- a/cmd/trade.go +++ b/cmd/trade.go @@ -208,16 +208,16 @@ func init() { }() } + strategyFillHandlers, e := strat.GetFillHandlers() + if e != nil { + log.Println() + log.Printf("problem encountered while instantiating the fill tracker: %s\n", e) + deleteAllOffersAndExit(botConfig, client, sdex) + } if botConfig.FillTrackerSleepMillis != 0 { fillTracker := plugins.MakeFillTracker(tradingPair, threadTracker, sdex, botConfig.FillTrackerSleepMillis) fillLogger := plugins.MakeFillLogger() fillTracker.RegisterHandler(fillLogger) - strategyFillHandlers, e := strat.GetFillHandlers() - if e != nil { - log.Println() - log.Printf("problem encountered while instantiating the fill tracker: %s\n", e) - deleteAllOffersAndExit(botConfig, client, sdex) - } if strategyFillHandlers != nil { for _, h := range strategyFillHandlers { fillTracker.RegisterHandler(h) @@ -234,6 +234,11 @@ func init() { deleteAllOffersAndExit(botConfig, client, sdex) } }() + } else if strategyFillHandlers != nil && len(strategyFillHandlers) > 0 { + log.Println() + log.Printf("error: strategy has FillHandlers but fill tracking was disabled (set FILL_TRACKER_SLEEP_MILLIS to a non-zero value)\n") + // we want to delete all the offers and exit here because we don't want the bot to run if fill tracking isn't working + deleteAllOffersAndExit(botConfig, client, sdex) } // --- end initialization of services --- diff --git a/plugins/balancedLevelProvider.go b/plugins/balancedLevelProvider.go index 609e90c5c..94f304862 100644 --- a/plugins/balancedLevelProvider.go +++ b/plugins/balancedLevelProvider.go @@ -1,6 +1,7 @@ package plugins import ( + "fmt" "log" "math/rand" "time" @@ -27,14 +28,21 @@ type balancedLevelProvider struct { virtualBalanceBase float64 // virtual balance to use so we can smoothen out the curve virtualBalanceQuote float64 // virtual balance to use so we can smoothen out the curve orderConstraints *model.OrderConstraints + shouldRefresh bool // boolean for whether to generate levels, starts true // precomputed before construction randGen *rand.Rand + + // uninitialized + lastLevels []api.Level // keeps the levels generated on the previous run to use if no offers were taken } // ensure it implements LevelProvider var _ api.LevelProvider = &balancedLevelProvider{} +// ensure this implements api.FillHandler +var _ api.FillHandler = &balancedLevelProvider{} + // makeBalancedLevelProvider is the factory method func makeBalancedLevelProvider( spread float64, @@ -69,8 +77,10 @@ func makeBalancedLevelProvider( validateSpread(carryoverInclusionProbability) randGen := rand.New(rand.NewSource(time.Now().UnixNano())) + shouldRefresh := true + return &balancedLevelProvider{ - spread: spread, + spread: spread, useMaxQuoteInTargetAmountCalc: useMaxQuoteInTargetAmountCalc, minAmountSpread: minAmountSpread, maxAmountSpread: maxAmountSpread, @@ -84,6 +94,7 @@ func makeBalancedLevelProvider( virtualBalanceQuote: virtualBalanceQuote, orderConstraints: orderConstraints, randGen: randGen, + shouldRefresh: shouldRefresh, } } @@ -95,35 +106,19 @@ func validateSpread(spread float64) { // GetLevels impl. func (p *balancedLevelProvider) GetLevels(maxAssetBase float64, maxAssetQuote float64) ([]api.Level, error) { - _maxAssetBase := maxAssetBase + p.virtualBalanceBase - _maxAssetQuote := maxAssetQuote + p.virtualBalanceQuote - // represents the amount that was meant to be included in a previous level that we excluded because we skipped that level - amountCarryover := 0.0 - levels := []api.Level{} - for i := int16(0); i < p.maxLevels; i++ { - level, e := p.getLevel(_maxAssetBase, _maxAssetQuote) - if e != nil { - return nil, e - } - - // always update _maxAssetBase and _maxAssetQuote to account for the level we just calculated, ensures price moves across levels regardless of inclusion of prior levels - _maxAssetBase, _maxAssetQuote = updateAssetBalances(level, p.useMaxQuoteInTargetAmountCalc, _maxAssetBase, _maxAssetQuote) + if !p.shouldRefresh { + log.Println("no offers were taken, leave levels as they are") + return p.lastLevels, nil + } - // always take a spread off the amountCarryover - amountCarryoverSpread := p.getRandomSpread(p.minAmountCarryoverSpread, p.maxAmountCarryoverSpread) - amountCarryover *= (1 - amountCarryoverSpread) + levels, e := p.recomputeLevels(maxAssetBase, maxAssetQuote) + if e != nil { + return nil, fmt.Errorf("unable to generate new levels: %s", e) + } - if !p.shouldIncludeLevel(i) { - // accummulate targetAmount into amountCarryover - amountCarryover += level.Amount.AsFloat() - continue - } + p.lastLevels = levels + p.shouldRefresh = false - if p.shouldIncludeCarryover() { - level, amountCarryover = p.computeNewLevelWithCarryover(level, amountCarryover) - } - levels = append(levels, level) - } return levels, nil } @@ -207,5 +202,45 @@ func (p *balancedLevelProvider) getLevel(maxAssetBase float64, maxAssetQuote flo // GetFillHandlers impl func (p *balancedLevelProvider) GetFillHandlers() ([]api.FillHandler, error) { - return nil, nil + return []api.FillHandler{p}, nil +} + +// HandleFill impl +func (p *balancedLevelProvider) HandleFill(trade model.Trade) error { + log.Println("an offer was taken, levels will be recomputed") + p.shouldRefresh = true + return nil +} + +func (p *balancedLevelProvider) recomputeLevels(maxAssetBase float64, maxAssetQuote float64) ([]api.Level, error) { + _maxAssetBase := maxAssetBase + p.virtualBalanceBase + _maxAssetQuote := maxAssetQuote + p.virtualBalanceQuote + // represents the amount that was meant to be included in a previous level that we excluded because we skipped that level + amountCarryover := 0.0 + levels := []api.Level{} + for i := int16(0); i < p.maxLevels; i++ { + level, e := p.getLevel(_maxAssetBase, _maxAssetQuote) + if e != nil { + return nil, e + } + + // always update _maxAssetBase and _maxAssetQuote to account for the level we just calculated, ensures price moves across levels regardless of inclusion of prior levels + _maxAssetBase, _maxAssetQuote = updateAssetBalances(level, p.useMaxQuoteInTargetAmountCalc, _maxAssetBase, _maxAssetQuote) + + // always take a spread off the amountCarryover + amountCarryoverSpread := p.getRandomSpread(p.minAmountCarryoverSpread, p.maxAmountCarryoverSpread) + amountCarryover *= (1 - amountCarryoverSpread) + + if !p.shouldIncludeLevel(i) { + // accummulate targetAmount into amountCarryover + amountCarryover += level.Amount.AsFloat() + continue + } + + if p.shouldIncludeCarryover() { + level, amountCarryover = p.computeNewLevelWithCarryover(level, amountCarryover) + } + levels = append(levels, level) + } + return levels, nil }