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

Commit be1f917

Browse files
authored
improve precision for BTC markets, can still improve precision further (related to #405)
1 parent b0d8617 commit be1f917

3 files changed

Lines changed: 161 additions & 8 deletions

File tree

model/number.go

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"log"
66
"math"
7+
"math/big"
78
"strconv"
89

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

2122
// InvertPrecision is the precision of the number after it is inverted
22-
const InvertPrecision = 15
23+
// 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
24+
const InvertPrecision = 11
2325

2426
// InternalCalculationsPrecision is the precision to be used for internal calculations in a function
2527
const InternalCalculationsPrecision = 15
@@ -157,7 +159,19 @@ func InvertNumber(n *Number) *Number {
157159
if n == nil {
158160
return nil
159161
}
160-
return NumberFromFloat(1.0/n.AsFloat(), InvertPrecision)
162+
163+
// return 0 for the inverse of 0 to keep it safe
164+
if n.AsFloat() == 0 {
165+
log.Printf("trying to invert the number 0, returning the same number to keep it safe")
166+
return n
167+
}
168+
169+
bigNum := big.NewRat(1, 1)
170+
bigNum = bigNum.SetFloat64(n.AsFloat())
171+
bigInv := bigNum.Inv(bigNum)
172+
173+
bigInvFloat64, _ := bigInv.Float64()
174+
return NumberFromFloat(bigInvFloat64, InvertPrecision)
161175
}
162176

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

@@ -189,8 +202,26 @@ const (
189202
)
190203

191204
func toFixed(num float64, precision int8, rounding Rounding) float64 {
192-
output := math.Pow(10, float64(precision))
193-
return float64(round(num*output, rounding)) / output
205+
bigNum := big.NewRat(1, 1)
206+
bigNum = bigNum.SetFloat64(num)
207+
bigPow := big.NewRat(1, 1)
208+
bigPow = bigPow.SetFloat64(math.Pow(10, float64(precision)))
209+
210+
// multiply
211+
bigMultiply := bigNum.Mul(bigNum, bigPow)
212+
213+
// convert to int after rounding
214+
bigMultiplyFloat64, _ := bigMultiply.Float64()
215+
roundedInt64 := round(bigMultiplyFloat64, rounding)
216+
bigMultiplyIntFloat64 := big.NewRat(1, 1)
217+
bigMultiplyIntFloat64 = bigMultiplyIntFloat64.SetInt64(roundedInt64)
218+
219+
// divide it
220+
bigPowInverse := bigPow.Inv(bigPow)
221+
bigResult := bigMultiply.Mul(bigMultiplyIntFloat64, bigPowInverse)
222+
223+
br, _ := bigResult.Float64()
224+
return br
194225
}
195226

196227
func minPrecision(n1 Number, n2 Number) int8 {

model/number_test.go

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ func TestNumberFromFloat(t *testing.T) {
3939
precision: 1,
4040
wantString: "0.1",
4141
wantFloat: 0.1,
42+
}, {
43+
f: 50000.0,
44+
precision: 14,
45+
wantString: "50000.00000000000000",
46+
wantFloat: 50000.0,
4247
},
4348
}
4449

@@ -103,6 +108,68 @@ func TestNumberFromFloatRoundTruncate(t *testing.T) {
103108
}
104109
}
105110

111+
func TestToFixed(t *testing.T) {
112+
testCases := []struct {
113+
num float64
114+
precision int8
115+
rounding Rounding
116+
wantOut float64
117+
}{
118+
// precision 5
119+
{
120+
num: 50000.12345,
121+
precision: 5,
122+
rounding: RoundUp,
123+
wantOut: 50000.12345,
124+
}, {
125+
num: 50000.12345,
126+
precision: 5,
127+
rounding: RoundTruncate,
128+
wantOut: 50000.12345,
129+
}, {
130+
num: 0.00002,
131+
precision: 5,
132+
rounding: RoundUp,
133+
wantOut: 0.00002,
134+
}, {
135+
num: 0.00002,
136+
precision: 5,
137+
rounding: RoundTruncate,
138+
wantOut: 0.00002,
139+
},
140+
// precision 4
141+
{
142+
num: 50000.12345,
143+
precision: 4,
144+
rounding: RoundUp,
145+
wantOut: 50000.1235,
146+
}, {
147+
num: 50000.12345,
148+
precision: 4,
149+
rounding: RoundTruncate,
150+
wantOut: 50000.1234,
151+
}, {
152+
num: 0.00002,
153+
precision: 4,
154+
rounding: RoundUp,
155+
wantOut: 0.0000, // we do not round the 2 up, if it was a 5 then we would round it up
156+
}, {
157+
num: 0.00002,
158+
precision: 4,
159+
rounding: RoundTruncate,
160+
wantOut: 0.0000,
161+
},
162+
}
163+
164+
for i, k := range testCases {
165+
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
166+
actual := toFixed(k.num, k.precision, k.rounding)
167+
168+
assert.Equal(t, k.wantOut, actual)
169+
})
170+
}
171+
}
172+
106173
func TestMath(t *testing.T) {
107174
testCases := []struct {
108175
n1 *Number
@@ -301,7 +368,12 @@ func TestUnaryOperations(t *testing.T) {
301368
n: NumberFromFloat(0.2812, 3),
302369
wantAbs: 0.281,
303370
wantNegate: -0.281,
304-
wantInvert: 3.558718861209964,
371+
wantInvert: 3.55871886121,
372+
}, {
373+
n: NumberFromFloat(0.00002, 10),
374+
wantAbs: 0.00002,
375+
wantNegate: -0.00002,
376+
wantInvert: 50000.0,
305377
},
306378
}
307379

@@ -321,7 +393,7 @@ func TestUnaryOperations(t *testing.T) {
321393
if !assert.Equal(t, kase.wantInvert, inverted.AsFloat()) {
322394
return
323395
}
324-
if !assert.Equal(t, int8(15), inverted.precision) {
396+
if !assert.Equal(t, int8(11), inverted.precision) {
325397
return
326398
}
327399
})

plugins/batchedExchange_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package plugins
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
9+
"github.com/stellar/go/txnbuild"
10+
"github.com/stellar/kelp/model"
11+
"github.com/stellar/kelp/support/utils"
12+
)
13+
14+
func TestManageOffer2Order(t *testing.T) {
15+
testCases := []struct {
16+
op *txnbuild.ManageSellOffer
17+
oc *model.OrderConstraints
18+
wantAction model.OrderAction
19+
wantAmount float64
20+
wantPrice float64
21+
}{
22+
{
23+
op: makeSellOpAmtPrice(0.0018, 50500.0),
24+
oc: model.MakeOrderConstraints(2, 4, 0.001),
25+
wantAction: model.OrderActionSell,
26+
wantAmount: 0.0018,
27+
wantPrice: 50500.0,
28+
}, {
29+
op: makeBuyOpAmtPrice(0.0018, 50500.0),
30+
oc: model.MakeOrderConstraints(2, 4, 0.001),
31+
wantAction: model.OrderActionBuy,
32+
wantAmount: 0.0018,
33+
// 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
34+
wantPrice: 50505.05,
35+
},
36+
}
37+
38+
for _, k := range testCases {
39+
baseAsset := utils.Asset2Asset2(testBaseAsset)
40+
quoteAsset := utils.Asset2Asset2(testQuoteAsset)
41+
order, e := manageOffer2Order(k.op, baseAsset, quoteAsset, k.oc)
42+
if !assert.NoError(t, e) {
43+
return
44+
}
45+
46+
assert.Equal(t, k.wantAction, order.OrderAction, fmt.Sprintf("expected '%s' but got '%s'", k.wantAction.String(), order.OrderAction.String()))
47+
assert.Equal(t, k.wantPrice, order.Price.AsFloat())
48+
assert.Equal(t, k.wantAmount, order.Volume.AsFloat())
49+
}
50+
}

0 commit comments

Comments
 (0)