mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-06-02 23:16:51 +00:00
Add orderbook calculator and verify func
This commit is contained in:
@@ -725,7 +725,10 @@ func (e *Base) IsAssetTypeSupported(asset asset.Item) bool {
|
||||
// PrintEnabledPairs prints the exchanges enabled asset pairs
|
||||
func (e *Base) PrintEnabledPairs() {
|
||||
for k, v := range e.CurrencyPairs.Pairs {
|
||||
log.Infof("Asset type %v:", k)
|
||||
log.Infof("\t Enabled pairs: %v", v.Enabled)
|
||||
log.Infof("%s Asset type %v:\n\t Enabled pairs: %v",
|
||||
e.Name, strings.ToUpper(k.String()), v.Enabled)
|
||||
}
|
||||
}
|
||||
|
||||
// GetBase returns the exchange base
|
||||
func (e *Base) GetBase() *Base { return e }
|
||||
|
||||
@@ -63,4 +63,5 @@ type IBotExchange interface {
|
||||
GetDefaultConfig() (*config.ExchangeConfig, error)
|
||||
GetSubscriptions() ([]WebsocketChannelSubscription, error)
|
||||
AuthenticateWebsocket() error
|
||||
GetBase() *Base
|
||||
}
|
||||
|
||||
227
exchanges/orderbook/calculator.go
Normal file
227
exchanges/orderbook/calculator.go
Normal file
@@ -0,0 +1,227 @@
|
||||
package orderbook
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
math "github.com/thrasher-/gocryptotrader/common/math"
|
||||
log "github.com/thrasher-/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
// 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 (o *Base) WhaleBomb(priceTarget float64, buy bool) (*WhaleBombResult, error) {
|
||||
if priceTarget < 0 {
|
||||
return nil, errors.New("price target is invalid")
|
||||
}
|
||||
if buy {
|
||||
a, orders := o.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, o.Pair.Quote.String(), o.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 := o.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, o.Pair.Base.String(), o.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 (o *Base) SimulateOrder(amount float64, buy bool) *OrderSimulationResult {
|
||||
if buy {
|
||||
orders, amt := o.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, o.Pair.Quote.String(), o.Pair.Base.String(), min, max,
|
||||
pct, len(orders))
|
||||
return &OrderSimulationResult{
|
||||
Orders: orders,
|
||||
Amount: amt,
|
||||
MinimumPrice: min,
|
||||
MaximumPrice: max,
|
||||
PercentageGainOrLoss: pct,
|
||||
Status: status,
|
||||
}
|
||||
}
|
||||
orders, amt := o.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, o.Pair.Base.String(), o.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("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 (o *Base) findAmount(price float64, buy bool) (float64, orderSummary) {
|
||||
var orders orderSummary
|
||||
var amt float64
|
||||
|
||||
if buy {
|
||||
asks := o.Asks
|
||||
for x := range asks {
|
||||
if asks[x].Price >= price {
|
||||
amt += asks[x].Price * asks[x].Amount
|
||||
orders = append(orders, Item{
|
||||
Price: asks[x].Price,
|
||||
Amount: asks[x].Amount,
|
||||
})
|
||||
return amt, orders
|
||||
}
|
||||
orders = append(orders, Item{
|
||||
Price: asks[x].Price,
|
||||
Amount: asks[x].Amount,
|
||||
})
|
||||
amt += asks[x].Price * asks[x].Amount
|
||||
}
|
||||
return amt, orders
|
||||
}
|
||||
|
||||
for x := range o.Bids {
|
||||
if o.Bids[x].Price <= price {
|
||||
amt += o.Bids[x].Amount
|
||||
orders = append(orders, Item{
|
||||
Price: o.Bids[x].Price,
|
||||
Amount: o.Bids[x].Amount,
|
||||
})
|
||||
break
|
||||
}
|
||||
orders = append(orders, Item{
|
||||
Price: o.Bids[x].Price,
|
||||
Amount: o.Bids[x].Amount,
|
||||
})
|
||||
amt += o.Bids[x].Amount
|
||||
}
|
||||
return amt, orders
|
||||
}
|
||||
|
||||
func (o *Base) buy(amount float64) (orders orderSummary, baseAmount float64) {
|
||||
var processedAmt float64
|
||||
for x := range o.Asks {
|
||||
subtotal := o.Asks[x].Price * o.Asks[x].Amount
|
||||
if processedAmt+subtotal >= amount {
|
||||
diff := amount - processedAmt
|
||||
subAmt := diff / o.Asks[x].Price
|
||||
orders = append(orders, Item{
|
||||
Price: o.Asks[x].Price,
|
||||
Amount: subAmt,
|
||||
})
|
||||
baseAmount += subAmt
|
||||
break
|
||||
}
|
||||
processedAmt += subtotal
|
||||
baseAmount += o.Asks[x].Amount
|
||||
orders = append(orders, Item{
|
||||
Price: o.Asks[x].Price,
|
||||
Amount: o.Asks[x].Amount,
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (o *Base) sell(amount float64) (orders orderSummary, quoteAmount float64) {
|
||||
var processedAmt float64
|
||||
for x := range o.Bids {
|
||||
if processedAmt+o.Bids[x].Amount >= amount {
|
||||
diff := amount - processedAmt
|
||||
orders = append(orders, Item{
|
||||
Price: o.Bids[x].Price,
|
||||
Amount: diff,
|
||||
})
|
||||
quoteAmount += diff * o.Bids[x].Price
|
||||
break
|
||||
}
|
||||
processedAmt += o.Bids[x].Amount
|
||||
quoteAmount += o.Bids[x].Amount * o.Bids[x].Price
|
||||
orders = append(orders, Item{
|
||||
Price: o.Bids[x].Price,
|
||||
Amount: o.Bids[x].Amount,
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
79
exchanges/orderbook/calculator_test.go
Normal file
79
exchanges/orderbook/calculator_test.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package orderbook
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/thrasher-/gocryptotrader/currency"
|
||||
)
|
||||
|
||||
func testSetup() Base {
|
||||
return Base{
|
||||
ExchangeName: "a",
|
||||
Pair: currency.NewPair(currency.BTC, currency.USD),
|
||||
Asks: []Item{
|
||||
{Price: 7000, Amount: 1},
|
||||
{Price: 7001, Amount: 2},
|
||||
},
|
||||
Bids: []Item{
|
||||
{Price: 6999, Amount: 1},
|
||||
{Price: 6998, Amount: 2},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestWhaleBomb(t *testing.T) {
|
||||
t.Parallel()
|
||||
b := testSetup()
|
||||
|
||||
// invalid price amout
|
||||
_, err := b.WhaleBomb(-1, true)
|
||||
if err == nil {
|
||||
t.Error("unexpected result")
|
||||
}
|
||||
|
||||
// valid
|
||||
b.WhaleBomb(7001, true)
|
||||
// invalid
|
||||
b.WhaleBomb(7002, true)
|
||||
|
||||
// valid
|
||||
b.WhaleBomb(6998, false)
|
||||
// invalid
|
||||
b.WhaleBomb(6997, false)
|
||||
}
|
||||
|
||||
func TestSimulateOrder(t *testing.T) {
|
||||
t.Parallel()
|
||||
b := testSetup()
|
||||
b.SimulateOrder(8000, true)
|
||||
b.SimulateOrder(1.5, false)
|
||||
}
|
||||
|
||||
func TestOrderSummary(t *testing.T) {
|
||||
var o orderSummary
|
||||
if p := o.MaximumPrice(false); p != 0 {
|
||||
t.Error("unexpected result")
|
||||
}
|
||||
if p := o.MinimumPrice(false); p != 0 {
|
||||
t.Error("unexpected result")
|
||||
}
|
||||
|
||||
o = orderSummary{
|
||||
{Price: 1337, Amount: 1},
|
||||
{Price: 9001, Amount: 1},
|
||||
}
|
||||
if p := o.MaximumPrice(false); p != 1337 {
|
||||
t.Error("unexpected result")
|
||||
}
|
||||
if p := o.MaximumPrice(true); p != 9001 {
|
||||
t.Error("unexpected result")
|
||||
}
|
||||
if p := o.MinimumPrice(false); p != 1337 {
|
||||
t.Error("unexpected result")
|
||||
}
|
||||
if p := o.MinimumPrice(true); p != 9001 {
|
||||
t.Error("unexpected result")
|
||||
}
|
||||
|
||||
o.Print()
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package orderbook
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -47,6 +48,45 @@ type Orderbook struct {
|
||||
ExchangeName string
|
||||
}
|
||||
|
||||
type byOBPrice []Item
|
||||
|
||||
func (a byOBPrice) Len() int { return len(a) }
|
||||
func (a byOBPrice) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a byOBPrice) Less(i, j int) bool { return a[i].Price < a[j].Price }
|
||||
|
||||
// Verify ensures that the orderbook items are correctly sorted
|
||||
// Bids should always go from a high price to a low price and
|
||||
// asks should always go from a low price to a higher price
|
||||
func (o *Base) Verify() {
|
||||
var lastPrice float64
|
||||
var sortBids, sortAsks bool
|
||||
for x := range o.Bids {
|
||||
if lastPrice != 0 && o.Bids[x].Price >= lastPrice {
|
||||
sortBids = true
|
||||
break
|
||||
}
|
||||
lastPrice = o.Bids[x].Price
|
||||
}
|
||||
|
||||
lastPrice = 0
|
||||
for x := range o.Asks {
|
||||
if lastPrice != 0 && o.Asks[x].Price <= lastPrice {
|
||||
sortAsks = true
|
||||
break
|
||||
}
|
||||
lastPrice = o.Asks[x].Price
|
||||
}
|
||||
|
||||
if sortBids {
|
||||
sort.Sort(sort.Reverse(byOBPrice(o.Bids)))
|
||||
}
|
||||
|
||||
if sortAsks {
|
||||
sort.Sort((byOBPrice(o.Asks)))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// TotalBidsAmount returns the total amount of bids and the total orderbook
|
||||
// bids value
|
||||
func (o *Base) TotalBidsAmount() (amountCollated, total float64) {
|
||||
@@ -168,6 +208,8 @@ func (o *Base) Process() error {
|
||||
o.LastUpdated = time.Now()
|
||||
}
|
||||
|
||||
o.Verify()
|
||||
|
||||
orderbook, err := GetByExchange(o.ExchangeName)
|
||||
if err != nil {
|
||||
CreateNewOrderbook(o.ExchangeName, o, o.AssetType)
|
||||
|
||||
@@ -12,6 +12,28 @@ import (
|
||||
log "github.com/thrasher-/gocryptotrader/logger"
|
||||
)
|
||||
|
||||
func TestVerify(t *testing.T) {
|
||||
t.Parallel()
|
||||
b := Base{
|
||||
ExchangeName: "TestExchange",
|
||||
Pair: currency.NewPair(currency.BTC, currency.USD),
|
||||
Bids: []Item{
|
||||
{Price: 100}, {Price: 101}, {Price: 99},
|
||||
},
|
||||
Asks: []Item{
|
||||
{Price: 100}, {Price: 99}, {Price: 101},
|
||||
},
|
||||
}
|
||||
|
||||
b.Verify()
|
||||
if r := b.Bids[1].Price; r != 100 {
|
||||
t.Error("unexpected result")
|
||||
}
|
||||
if r := b.Asks[1].Price; r != 100 {
|
||||
t.Error("unexpected result")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalculateTotalBids(t *testing.T) {
|
||||
t.Parallel()
|
||||
currency := currency.NewPairFromStrings("BTC", "USD")
|
||||
|
||||
24
exchanges/orderbook/simulator/simulator_test.go
Normal file
24
exchanges/orderbook/simulator/simulator_test.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package simulator
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/thrasher-/gocryptotrader/currency"
|
||||
"github.com/thrasher-/gocryptotrader/exchanges/asset"
|
||||
"github.com/thrasher-/gocryptotrader/exchanges/bitstamp"
|
||||
)
|
||||
|
||||
func TestSimulate(t *testing.T) {
|
||||
b := bitstamp.Bitstamp{}
|
||||
b.SetDefaults()
|
||||
b.Verbose = false
|
||||
o, err := b.FetchOrderbook(currency.NewPair(currency.BTC, currency.USD), asset.Spot)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
r := o.SimulateOrder(10000000, true)
|
||||
t.Log(r.Status)
|
||||
r = o.SimulateOrder(2171, false)
|
||||
t.Log(r.Status)
|
||||
}
|
||||
Reference in New Issue
Block a user