Skip to content

Commit

Permalink
Price logic refactoring and swap processing optimization (#68)
Browse files Browse the repository at this point in the history
  • Loading branch information
AndreyMashukov authored Jul 5, 2024
1 parent 102e31d commit d3b90be
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 94 deletions.
22 changes: 12 additions & 10 deletions src/config/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,13 +165,20 @@ func InitServiceContainer() Container {
log.Panic(fmt.Sprintf("Unsupported exchange: %s", botExchange))
}

exchangeRepository := repository.ExchangeRepository{
objectRepository := repository.ObjectRepository{
DB: db,
CurrentBot: currentBot,
RDB: rdb,
Ctx: &ctx,
CurrentBot: currentBot,
Formatter: &formatter,
Binance: exchangeApi,
}
exchangeRepository := repository.ExchangeRepository{
DB: db,
RDB: rdb,
Ctx: &ctx,
CurrentBot: currentBot,
Formatter: &formatter,
Binance: exchangeApi,
ObjectRepository: &objectRepository,
}

marketDepthStrategy := strategy.MarketDepthStrategy{}
Expand All @@ -185,12 +192,6 @@ func InitServiceContainer() Container {
Formatter: &formatter,
Binance: exchangeApi,
}
objectRepository := repository.ObjectRepository{
DB: db,
CurrentBot: currentBot,
RDB: rdb,
Ctx: &ctx,
}
swapRepository := repository.SwapRepository{
DB: swapDb,
RDB: rdb,
Expand Down Expand Up @@ -500,6 +501,7 @@ func InitServiceContainer() Container {
SwapDb: swapDb,
RDB: rdb,
Ctx: &ctx,
TimeService: &timeService,
}

botController := controller.BotController{
Expand Down
1 change: 1 addition & 0 deletions src/model/bot_health.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@ type BotHealth struct {
OrderBook map[string]string `json:"orderBook"`
GOMAXPROCS int `json:"GOMAXPROCS"`
NumGoroutine int `json:"numGoroutine"`
DateTimeNow string `json:"dateTimeNow"`
}
8 changes: 0 additions & 8 deletions src/model/kline.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package model

import (
"log"
"math"
"time"
)
Expand Down Expand Up @@ -128,13 +127,6 @@ func (k *KLine) Update(ticker MiniTicker) KLine {
// This is daily ticker price, we can use only `ticker.Close` for minute KLines!
currentInterval := TimestampMilli(time.Now().UnixMilli()).GetPeriodToMinute()
if k.Timestamp.GetPeriodToMinute() < currentInterval {
log.Printf(
"[%s] New time interval reached %d -> %d, price is unknown",
k.Symbol,
k.Timestamp.GetPeriodToMinute(),
currentInterval,
)

return KLine{
Timestamp: TimestampMilli(currentInterval),
Symbol: ticker.Symbol,
Expand Down
48 changes: 24 additions & 24 deletions src/repository/exhange_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,13 @@ type ExchangePriceStorageInterface interface {
}

type ExchangeRepository struct {
DB *sql.DB
RDB *redis.Client
Ctx *context.Context
CurrentBot *model.Bot
Formatter *utils.Formatter
Binance client.ExchangePriceAPIInterface
DB *sql.DB
RDB *redis.Client
Ctx *context.Context
CurrentBot *model.Bot
Formatter *utils.Formatter
Binance client.ExchangePriceAPIInterface
ObjectRepository *ObjectRepository
}

func (e *ExchangeRepository) GetSubscribedSymbols() []model.Symbol {
Expand Down Expand Up @@ -694,26 +695,26 @@ func (e *ExchangeRepository) UpdateTradeLimit(limit model.TradeLimit) error {
return nil
}

func (e *ExchangeRepository) GetCurrentKline(symbol string) *model.KLine {
encodedLast := e.RDB.Get(*e.Ctx, fmt.Sprintf("last-kline-%s-%d", symbol, e.CurrentBot.Id)).Val()

if len(encodedLast) > 0 {
var dto model.KLine
err := json.Unmarshal([]byte(encodedLast), &dto)
func (e *ExchangeRepository) GetKlineKey(symbol string) string {
return fmt.Sprintf("current-kline-%s-%d", symbol, e.CurrentBot.Id)
}

if err == nil {
tradeVolume := e.GetTradeVolume(dto.Symbol, dto.Timestamp)
if tradeVolume != nil {
dto.TradeVolume = tradeVolume
}
func (e *ExchangeRepository) GetCurrentKline(symbol string) *model.KLine {
var kLine model.KLine
err := e.ObjectRepository.LoadObject(e.GetKlineKey(symbol), &kLine)

priceChangeSpeed := e.GetPriceChangeSpeed(dto.Symbol, dto.Timestamp)
if priceChangeSpeed != nil {
dto.PriceChangeSpeed = priceChangeSpeed
}
if err == nil {
tradeVolume := e.GetTradeVolume(kLine.Symbol, kLine.Timestamp)
if tradeVolume != nil {
kLine.TradeVolume = tradeVolume
}

return &dto
priceChangeSpeed := e.GetPriceChangeSpeed(kLine.Symbol, kLine.Timestamp)
if priceChangeSpeed != nil {
kLine.PriceChangeSpeed = priceChangeSpeed
}

return &kLine
}

return nil
Expand Down Expand Up @@ -754,9 +755,8 @@ func (e *ExchangeRepository) SetCurrentKline(kLine model.KLine) {

kLine.PriceChangeSpeed = priceChangeSpeed
e.SetPriceChangeSpeed(*priceChangeSpeed)
encoded, _ := json.Marshal(kLine)

e.RDB.Set(*e.Ctx, fmt.Sprintf("last-kline-%s-%d", kLine.Symbol, e.CurrentBot.Id), string(encoded), time.Hour)
_ = e.ObjectRepository.SaveObject(e.GetKlineKey(kLine.Symbol), kLine)
}

func (e *ExchangeRepository) SaveKlineHistory(kLine model.KLine) {
Expand Down
9 changes: 5 additions & 4 deletions src/service/exchange/maker_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,15 @@ type MakerService struct {
}

func (m *MakerService) Make(symbol string) {
decision, err := m.StrategyFacade.Decide(symbol)
openedOrder := m.OrderRepository.GetOpenedOrderCached(symbol, "BUY")

if err != nil {
if openedOrder != nil && m.OrderExecutor.ProcessSwap(*openedOrder) {
return
}

openedOrder := m.OrderRepository.GetOpenedOrderCached(symbol, "BUY")
decision, err := m.StrategyFacade.Decide(symbol)

if openedOrder != nil && m.OrderExecutor.ProcessSwap(*openedOrder) {
if err != nil {
return
}

Expand Down Expand Up @@ -80,6 +80,7 @@ func (m *MakerService) ProcessBuy(tradeLimit model.TradeLimit) {
limitBuy := m.OrderRepository.GetBinanceOrder(tradeLimit.Symbol, "BUY")

if limitBuy != nil {
// todo: signal := m.SignalStorage.GetSignal(tradeLimit.Symbol)
priceModel := m.PriceCalculator.CalculateBuy(tradeLimit)

err := m.OrderExecutor.Buy(tradeLimit, limitBuy.Price, limitBuy.OrigQty, priceModel.Signal)
Expand Down
103 changes: 63 additions & 40 deletions src/service/exchange/order_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -452,17 +452,25 @@ func (m *OrderExecutor) Sell(tradeLimit model.TradeLimit, opened model.Order, pr
}

func (m *OrderExecutor) ProcessSwap(order model.Order) bool {
if m.BotService.IsSwapEnabled() && order.IsSwap() {
switch true {
case m.BotService.IsSwapEnabled() && order.IsSwap():
log.Printf("[%s] Swap Order [%d] Mode: processing...", order.Symbol, order.Id)
m.SwapExecutor.Execute(order)
return true
} else if m.BotService.IsSwapEnabled() {
case m.BotService.IsSwapEnabled():
possibleSwap := m.HasSwapOption(&order)
if possibleSwap != nil {
m.MakeSwap(order, *possibleSwap)
}

swapAction, err := m.SwapRepository.GetActiveSwapAction(order)
if err == nil && swapAction.OrderId == order.Id {
log.Printf("[%s] Swap Recovered for Order [%d] Mode: processing...", order.Symbol, order.Id)
log.Printf("[%s] Swap Order [%d] Mode: processing...", order.Symbol, order.Id)
m.SwapExecutor.Execute(order)
return true
}

break
}

return false
Expand Down Expand Up @@ -1123,6 +1131,52 @@ func (m *OrderExecutor) CheckIsTimeToSell(
return false
}

func (m *OrderExecutor) HasSwapOption(openedBuyPosition *model.Order) *model.SwapChainEntity {
swapChain := m.SwapRepository.GetSwapChainCache(openedBuyPosition.GetBaseAsset())
if swapChain != nil {
possibleSwaps := m.SwapRepository.GetSwapChains(openedBuyPosition.GetBaseAsset())

if len(possibleSwaps) == 0 {
m.SwapRepository.InvalidateSwapChainCache(openedBuyPosition.GetBaseAsset())
}

kline := m.ExchangeRepository.GetCurrentKline(openedBuyPosition.Symbol)

if kline == nil {
return nil
}

for _, possibleSwap := range possibleSwaps {
turboSwap := possibleSwap.Percent.Gte(model.Percent(m.TurboSwapProfitPercent))
isTimeToSwap := openedBuyPosition.GetPositionTime().GetMinutes() >= m.BotService.GetSwapConfig().OrderTimeTrigger.GetMinutes() && openedBuyPosition.GetProfitPercent(kline.Close, m.BotService.UseSwapCapital()).Lte(model.Percent(m.BotService.GetSwapConfig().FallPercentTrigger)) && !openedBuyPosition.IsSwap()

if !turboSwap && !isTimeToSwap {
break
}

violation := m.SwapValidator.Validate(possibleSwap, *openedBuyPosition)

if violation == nil {
chainCurrentPercent := m.SwapValidator.CalculatePercent(possibleSwap)
log.Printf(
"[%s] Swap chain [%s] is found for order #%d, initial percent: %.2f, current = %.2f",
openedBuyPosition.Symbol,
swapChain.Title,
openedBuyPosition.Id,
swapChain.Percent,
chainCurrentPercent,
)

return &possibleSwap
} else {
log.Printf("CheckIsTimeToSwap: %s", violation.Error())
}
}
}

return nil
}

func (m *OrderExecutor) CheckIsTimeToSwap(
binanceOrder *model.BinanceOrder,
orderManageChannel chan string,
Expand All @@ -1143,44 +1197,13 @@ func (m *OrderExecutor) CheckIsTimeToSwap(

// Try arbitrage for long orders >= 4 hours and with profit < -1.00%
if openedBuyPosition != nil {
swapChain := m.SwapRepository.GetSwapChainCache(openedBuyPosition.GetBaseAsset())
if swapChain != nil {
possibleSwaps := m.SwapRepository.GetSwapChains(openedBuyPosition.GetBaseAsset())

if len(possibleSwaps) == 0 {
m.SwapRepository.InvalidateSwapChainCache(openedBuyPosition.GetBaseAsset())
possibleSwap := m.HasSwapOption(openedBuyPosition)
if possibleSwap != nil {
swapCallback := func() {
m.MakeSwap(*openedBuyPosition, *possibleSwap)
}

for _, possibleSwap := range possibleSwaps {
turboSwap := possibleSwap.Percent.Gte(model.Percent(m.TurboSwapProfitPercent))
isTimeToSwap := openedBuyPosition.GetPositionTime().GetMinutes() >= m.BotService.GetSwapConfig().OrderTimeTrigger.GetMinutes() && openedBuyPosition.GetProfitPercent(kline.Close, m.BotService.UseSwapCapital()).Lte(model.Percent(m.BotService.GetSwapConfig().FallPercentTrigger)) && !openedBuyPosition.IsSwap()

if !turboSwap && !isTimeToSwap {
break
}

violation := m.SwapValidator.Validate(possibleSwap, *openedBuyPosition)

if violation == nil {
chainCurrentPercent := m.SwapValidator.CalculatePercent(possibleSwap)
log.Printf(
"[%s] Swap chain [%s] is found for order #%d, initial percent: %.2f, current = %.2f",
binanceOrder.Symbol,
swapChain.Title,
openedBuyPosition.Id,
swapChain.Percent,
chainCurrentPercent,
)

swapCallback := func() {
m.MakeSwap(*openedBuyPosition, possibleSwap)
}
if m.TryCancel(binanceOrder, orderManageChannel, control, swapCallback, true) {
return true
}
} else {
log.Printf("CheckIsTimeToSwap: %s", violation.Error())
}
if m.TryCancel(binanceOrder, orderManageChannel, control, swapCallback, true) {
return true
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions src/service/health_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"gitlab.com/open-soft/go-crypto-bot/src/model"
"gitlab.com/open-soft/go-crypto-bot/src/repository"
"gitlab.com/open-soft/go-crypto-bot/src/service/ml"
"gitlab.com/open-soft/go-crypto-bot/src/utils"
"runtime"
"time"
)
Expand All @@ -24,6 +25,7 @@ type HealthService struct {
Ctx *context.Context
Binance client.ExchangeAPIInterface
CurrentBot *model.Bot
TimeService utils.TimeServiceInterface
}

func (h *HealthService) HealthCheck() model.BotHealth {
Expand Down Expand Up @@ -102,5 +104,6 @@ func (h *HealthService) HealthCheck() model.BotHealth {
OrderBook: orderBookMap,
GOMAXPROCS: runtime.GOMAXPROCS(0),
NumGoroutine: runtime.NumGoroutine(),
DateTimeNow: h.TimeService.GetNowDateTimeString(),
}
}
9 changes: 1 addition & 8 deletions src/service/strategy/market_trade_listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,6 @@ func (m *MarketTradeListener) ListenAll() {
go func() {
for {
kLine := <-klineChannel
predictChannel <- kLine.Symbol

lastKline := m.ExchangeRepository.GetCurrentKline(kLine.Symbol)

if lastKline != nil && lastKline.Timestamp.Gt(kLine.Timestamp) {
Expand All @@ -84,6 +82,7 @@ func (m *MarketTradeListener) ListenAll() {
}, event.EventNewKLineReceived)
}

predictChannel <- kLine.Symbol
m.ExchangeRepository.SetDecision(m.BaseKLineStrategy.Decide(kLine), kLine.Symbol)
m.ExchangeRepository.SetDecision(m.OrderBasedStrategy.Decide(kLine), kLine.Symbol)
}
Expand Down Expand Up @@ -187,12 +186,6 @@ func (m *MarketTradeListener) ListenAll() {
k.Close = t.Price

if k.Timestamp.GetPeriodToMinute() < currentInterval {
log.Printf(
"[%s] New time interval reached %d -> %d, price is unknown",
k.Symbol,
k.Timestamp.GetPeriodToMinute(),
currentInterval,
)
k.Timestamp = model.TimestampMilli(currentInterval)
k.Open = t.Price
k.Close = t.Price
Expand Down

0 comments on commit d3b90be

Please sign in to comment.