package orderbook import ( "errors" "fmt" "sort" math "github.com/thrasher-corp/gocryptotrader/common/math" "github.com/thrasher-corp/gocryptotrader/log" ) // WhaleBombResult returns the whale bomb result type WhaleBombResult struct { Amount float64 MinimumPrice float64 MaximumPrice float64 PercentageGainOrLoss float64 Orders orderSummary Status string } // WhaleBomb finds the amount required to target a price func (b *Base) WhaleBomb(priceTarget float64, buy bool) (*WhaleBombResult, error) { if priceTarget < 0 { return nil, errors.New("price target is invalid") } if buy { a, orders := b.findAmount(priceTarget, true) min, max := orders.MinimumPrice(false), orders.MaximumPrice(true) var err error if max < priceTarget { err = errors.New("unable to hit price target due to insufficient orderbook items") } status := fmt.Sprintf("Buying %.2f %v worth of %v will send the price from %v to %v [%.2f%%] and take %v orders.", a, b.Pair.Quote.String(), b.Pair.Base.String(), min, max, math.CalculatePercentageGainOrLoss(max, min), len(orders)) return &WhaleBombResult{ Amount: a, Orders: orders, MinimumPrice: min, MaximumPrice: max, Status: status, }, err } a, orders := b.findAmount(priceTarget, false) min, max := orders.MinimumPrice(false), orders.MaximumPrice(true) var err error if min > priceTarget { err = errors.New("unable to hit price target due to insufficient orderbook items") } status := fmt.Sprintf("Selling %.2f %v worth of %v will send the price from %v to %v [%.2f%%] and take %v orders.", a, b.Pair.Base.String(), b.Pair.Quote.String(), max, min, math.CalculatePercentageGainOrLoss(min, max), len(orders)) return &WhaleBombResult{ Amount: a, Orders: orders, MinimumPrice: min, MaximumPrice: max, Status: status, }, err } // OrderSimulationResult returns the order simulation result type OrderSimulationResult WhaleBombResult // SimulateOrder simulates an order func (b *Base) SimulateOrder(amount float64, buy bool) *OrderSimulationResult { if buy { orders, amt := b.buy(amount) min, max := orders.MinimumPrice(false), orders.MaximumPrice(true) pct := math.CalculatePercentageGainOrLoss(max, min) status := fmt.Sprintf("Buying %.2f %v worth of %v will send the price from %v to %v [%.2f%%] and take %v orders.", amount, b.Pair.Quote.String(), b.Pair.Base.String(), min, max, pct, len(orders)) return &OrderSimulationResult{ Orders: orders, Amount: amt, MinimumPrice: min, MaximumPrice: max, PercentageGainOrLoss: pct, Status: status, } } orders, amt := b.sell(amount) min, max := orders.MinimumPrice(false), orders.MaximumPrice(true) pct := math.CalculatePercentageGainOrLoss(min, max) status := fmt.Sprintf("Selling %f %v worth of %v will send the price from %v to %v [%.2f%%] and take %v orders.", amount, b.Pair.Base.String(), b.Pair.Quote.String(), max, min, pct, len(orders)) return &OrderSimulationResult{ Orders: orders, Amount: amt, MinimumPrice: min, MaximumPrice: max, PercentageGainOrLoss: pct, Status: status, } } type orderSummary []Item func (o orderSummary) Print() { for x := range o { log.Debugf(log.OrderBook, "Order: Price: %f Amount: %f", o[x].Price, o[x].Amount) } } func (o orderSummary) MinimumPrice(reverse bool) float64 { if len(o) != 0 { sortOrdersByPrice(&o, reverse) return o[0].Price } return 0 } func (o orderSummary) MaximumPrice(reverse bool) float64 { if len(o) != 0 { sortOrdersByPrice(&o, reverse) return o[0].Price } return 0 } // ByPrice used for sorting orders by order date type ByPrice orderSummary func (b ByPrice) Len() int { return len(b) } func (b ByPrice) Less(i, j int) bool { return b[i].Price < b[j].Price } func (b ByPrice) Swap(i, j int) { b[i], b[j] = b[j], b[i] } // sortOrdersByPrice the caller function to sort orders func sortOrdersByPrice(o *orderSummary, reverse bool) { if reverse { sort.Sort(sort.Reverse(ByPrice(*o))) } else { sort.Sort(ByPrice(*o)) } } func (b *Base) findAmount(price float64, buy bool) (float64, orderSummary) { var orders orderSummary var amt float64 if buy { for x := range b.Asks { if b.Asks[x].Price >= price { amt += b.Asks[x].Price * b.Asks[x].Amount orders = append(orders, Item{ Price: b.Asks[x].Price, Amount: b.Asks[x].Amount, }) return amt, orders } orders = append(orders, Item{ Price: b.Asks[x].Price, Amount: b.Asks[x].Amount, }) amt += b.Asks[x].Price * b.Asks[x].Amount } return amt, orders } for x := range b.Bids { if b.Bids[x].Price <= price { amt += b.Bids[x].Amount orders = append(orders, Item{ Price: b.Bids[x].Price, Amount: b.Bids[x].Amount, }) break } orders = append(orders, Item{ Price: b.Bids[x].Price, Amount: b.Bids[x].Amount, }) amt += b.Bids[x].Amount } return amt, orders } func (b *Base) buy(amount float64) (orders orderSummary, baseAmount float64) { var processedAmt float64 for x := range b.Asks { subtotal := b.Asks[x].Price * b.Asks[x].Amount if processedAmt+subtotal >= amount { diff := amount - processedAmt subAmt := diff / b.Asks[x].Price orders = append(orders, Item{ Price: b.Asks[x].Price, Amount: subAmt, }) baseAmount += subAmt break } processedAmt += subtotal baseAmount += b.Asks[x].Amount orders = append(orders, Item{ Price: b.Asks[x].Price, Amount: b.Asks[x].Amount, }) } return } func (b *Base) sell(amount float64) (orders orderSummary, quoteAmount float64) { var processedAmt float64 for x := range b.Bids { if processedAmt+b.Bids[x].Amount >= amount { diff := amount - processedAmt orders = append(orders, Item{ Price: b.Bids[x].Price, Amount: diff, }) quoteAmount += diff * b.Bids[x].Price break } processedAmt += b.Bids[x].Amount quoteAmount += b.Bids[x].Amount * b.Bids[x].Price orders = append(orders, Item{ Price: b.Bids[x].Price, Amount: b.Bids[x].Amount, }) } return }