Skip to content
This repository has been archived by the owner on Feb 1, 2024. It is now read-only.

Commit

Permalink
improve precision for BTC markets, can still improve precision furthe…
Browse files Browse the repository at this point in the history
…r (related to #405)
  • Loading branch information
nikhilsaraf authored Mar 8, 2021
1 parent b0d8617 commit be1f917
Show file tree
Hide file tree
Showing 3 changed files with 161 additions and 8 deletions.
43 changes: 37 additions & 6 deletions model/number.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"log"
"math"
"math/big"
"strconv"

"github.com/stellar/go/price"
Expand All @@ -19,7 +20,8 @@ var NumberConstants = struct {
}

// InvertPrecision is the precision of the number after it is inverted
const InvertPrecision = 15
// this is only 11 becuase if we keep it larger such as 15 then inversions are inaccurate for larger numbers such as inverting 0.00002
const InvertPrecision = 11

// InternalCalculationsPrecision is the precision to be used for internal calculations in a function
const InternalCalculationsPrecision = 15
Expand Down Expand Up @@ -157,7 +159,19 @@ func InvertNumber(n *Number) *Number {
if n == nil {
return nil
}
return NumberFromFloat(1.0/n.AsFloat(), InvertPrecision)

// return 0 for the inverse of 0 to keep it safe
if n.AsFloat() == 0 {
log.Printf("trying to invert the number 0, returning the same number to keep it safe")
return n
}

bigNum := big.NewRat(1, 1)
bigNum = bigNum.SetFloat64(n.AsFloat())
bigInv := bigNum.Inv(bigNum)

bigInvFloat64, _ := bigInv.Float64()
return NumberFromFloat(bigInvFloat64, InvertPrecision)
}

// NumberByCappingPrecision returns a number with a precision that is at max the passed in precision
Expand All @@ -174,8 +188,7 @@ func round(num float64, rounding Rounding) int64 {
} else if rounding == RoundTruncate {
return int64(num)
} else {
// error
return -1
panic(fmt.Sprintf("unknown rounding type %v", rounding))
}
}

Expand All @@ -189,8 +202,26 @@ const (
)

func toFixed(num float64, precision int8, rounding Rounding) float64 {
output := math.Pow(10, float64(precision))
return float64(round(num*output, rounding)) / output
bigNum := big.NewRat(1, 1)
bigNum = bigNum.SetFloat64(num)
bigPow := big.NewRat(1, 1)
bigPow = bigPow.SetFloat64(math.Pow(10, float64(precision)))

// multiply
bigMultiply := bigNum.Mul(bigNum, bigPow)

// convert to int after rounding
bigMultiplyFloat64, _ := bigMultiply.Float64()
roundedInt64 := round(bigMultiplyFloat64, rounding)
bigMultiplyIntFloat64 := big.NewRat(1, 1)
bigMultiplyIntFloat64 = bigMultiplyIntFloat64.SetInt64(roundedInt64)

// divide it
bigPowInverse := bigPow.Inv(bigPow)
bigResult := bigMultiply.Mul(bigMultiplyIntFloat64, bigPowInverse)

br, _ := bigResult.Float64()
return br
}

func minPrecision(n1 Number, n2 Number) int8 {
Expand Down
76 changes: 74 additions & 2 deletions model/number_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ func TestNumberFromFloat(t *testing.T) {
precision: 1,
wantString: "0.1",
wantFloat: 0.1,
}, {
f: 50000.0,
precision: 14,
wantString: "50000.00000000000000",
wantFloat: 50000.0,
},
}

Expand Down Expand Up @@ -103,6 +108,68 @@ func TestNumberFromFloatRoundTruncate(t *testing.T) {
}
}

func TestToFixed(t *testing.T) {
testCases := []struct {
num float64
precision int8
rounding Rounding
wantOut float64
}{
// precision 5
{
num: 50000.12345,
precision: 5,
rounding: RoundUp,
wantOut: 50000.12345,
}, {
num: 50000.12345,
precision: 5,
rounding: RoundTruncate,
wantOut: 50000.12345,
}, {
num: 0.00002,
precision: 5,
rounding: RoundUp,
wantOut: 0.00002,
}, {
num: 0.00002,
precision: 5,
rounding: RoundTruncate,
wantOut: 0.00002,
},
// precision 4
{
num: 50000.12345,
precision: 4,
rounding: RoundUp,
wantOut: 50000.1235,
}, {
num: 50000.12345,
precision: 4,
rounding: RoundTruncate,
wantOut: 50000.1234,
}, {
num: 0.00002,
precision: 4,
rounding: RoundUp,
wantOut: 0.0000, // we do not round the 2 up, if it was a 5 then we would round it up
}, {
num: 0.00002,
precision: 4,
rounding: RoundTruncate,
wantOut: 0.0000,
},
}

for i, k := range testCases {
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
actual := toFixed(k.num, k.precision, k.rounding)

assert.Equal(t, k.wantOut, actual)
})
}
}

func TestMath(t *testing.T) {
testCases := []struct {
n1 *Number
Expand Down Expand Up @@ -301,7 +368,12 @@ func TestUnaryOperations(t *testing.T) {
n: NumberFromFloat(0.2812, 3),
wantAbs: 0.281,
wantNegate: -0.281,
wantInvert: 3.558718861209964,
wantInvert: 3.55871886121,
}, {
n: NumberFromFloat(0.00002, 10),
wantAbs: 0.00002,
wantNegate: -0.00002,
wantInvert: 50000.0,
},
}

Expand All @@ -321,7 +393,7 @@ func TestUnaryOperations(t *testing.T) {
if !assert.Equal(t, kase.wantInvert, inverted.AsFloat()) {
return
}
if !assert.Equal(t, int8(15), inverted.precision) {
if !assert.Equal(t, int8(11), inverted.precision) {
return
}
})
Expand Down
50 changes: 50 additions & 0 deletions plugins/batchedExchange_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package plugins

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"

"github.com/stellar/go/txnbuild"
"github.com/stellar/kelp/model"
"github.com/stellar/kelp/support/utils"
)

func TestManageOffer2Order(t *testing.T) {
testCases := []struct {
op *txnbuild.ManageSellOffer
oc *model.OrderConstraints
wantAction model.OrderAction
wantAmount float64
wantPrice float64
}{
{
op: makeSellOpAmtPrice(0.0018, 50500.0),
oc: model.MakeOrderConstraints(2, 4, 0.001),
wantAction: model.OrderActionSell,
wantAmount: 0.0018,
wantPrice: 50500.0,
}, {
op: makeBuyOpAmtPrice(0.0018, 50500.0),
oc: model.MakeOrderConstraints(2, 4, 0.001),
wantAction: model.OrderActionBuy,
wantAmount: 0.0018,
// 1/50500.0 = 0.000019801980198, we need to reduce it to 7 decimals precision because of sdex op, giving 0.0000198 which when inverted is 50505.05 at price precision = 2
wantPrice: 50505.05,
},
}

for _, k := range testCases {
baseAsset := utils.Asset2Asset2(testBaseAsset)
quoteAsset := utils.Asset2Asset2(testQuoteAsset)
order, e := manageOffer2Order(k.op, baseAsset, quoteAsset, k.oc)
if !assert.NoError(t, e) {
return
}

assert.Equal(t, k.wantAction, order.OrderAction, fmt.Sprintf("expected '%s' but got '%s'", k.wantAction.String(), order.OrderAction.String()))
assert.Equal(t, k.wantPrice, order.Price.AsFloat())
assert.Equal(t, k.wantAmount, order.Volume.AsFloat())
}
}

0 comments on commit be1f917

Please sign in to comment.